1. 23
  1. 11

    Awesome work! But I think you’re going to need to spend some time on your parser. This kind of lexical analysis and the way you rip apart and pull back syntax together as strings is… unorthodox. Well, I’m impressed how far it got you up to now. But it looks like there’s almost no user-friendly error handling where the parser points out the exact line and column number where the error happens.

    This is not at all an argument for using a parser generator. I think you’ll probably have nicer parsing code (and potential for friendly parsing errors) if you write a separate lexer step and have the parser functions just take an array of already lexed tokens.

    Thankfully parsing is normally the hard part and you’ve already done that! Lexing is the easy part.

    Also, are you keeping track of identifiers and type names in a scope? I didn’t notice anything that would generate suffixes to avoid name collisions between user names/types and your generated types. For example if a user created a type Result and also created a function Result. The generated TS would blow up?

    I installed it via yarn but couldn’t figure out how to actually run the compiler against an example file.

    Again though, awesome work. I love seeing projects like this.

    1. 3

      I’m writing up my next steps, which include some things like unfiying types between TS and Derw, which will be shared in a few days time. In terms of parsing/lexing, my plan is to actually rewrite the code in Derw as soon as I can - so I’m just getting to the point where I can bootstrap the project, then rewrite lexing/parsing/compiling in Derw code.

      To run the compiler, the easiest way is to use npx @eeue56/derw --files <some file> --output <some dir>. I haven’t rigorously tested this yet though.

      1. 1

        npx @eeue56/derw

        I don’t like installing random node tools (I know npx is common but still), just try to stick with yarn/npm commands. What does this do under the hood if I only have npm/yarn?

        1. 1

          npx is a command that is bundled with npm. It’s the equivalent of installing the package to a project directory and calling the binary as if it were global. You can do the equivalent thing with yarn just by calling yarn, e.g. yarn @eeue56/derw --files <some file> --output <some dir>

          1. 1

            You should be able to do npm install -g @eeue56/derw and then run it via derw --files <some file> --output <some dir>. I suggest using nvm if you do use -g though. And I haven’t tested yarn at all. The entry point is the cli.js file in the build directory.

            1. 1

              Cool, got it working. And yeah looks like your context system might need some work too:

              ➜  derw cat foo.derw
              type Result a b =
                  Err a { error: a }
                  | Ok b { value: b }
              
              Err: number -> number
              Err a =
                  case a of
                      1 ->
                          2
                      3 ->
                          3
              
              asIs: Result a b -> Result a b
              asIs result =
                  case result of
                      Err { error } ->
                          Err { error }
                      Ok { value } ->
                          Ok { value }
              ➜  derw cat out/foo.ts
              type Err<a> = {
                  kind: "Err";
                  a error: a;
              };
              
              function Err<a>(args: { a error: a }): Err<a> {
                  return {
                      kind: "Err",
                      ...args,
                  };
              }
              
              type Ok<b> = {
                  kind: "Ok";
                  b value: b;
              };
              
              function Ok<b>(args: { b value: b }): Ok<b> {
                  return {
                      kind: "Ok",
                      ...args,
                  };
              }
              
              type Result<a, b> = Err<a> | Ok<b>;
              
              function Err(a: number): number {
                  switch (a.kind) {
                      case "1": {
                          return 2;
                      }
                      case "3": {
                          return 3;
                      }
                  }
              }
              
              function asIs<a, b>(result: Result<a, b>): Result<a, b> {
                  switch (result.kind) {
                      case "Err": {
                          const { error } = result;
                          return Err({ error });
                      }
                      case "Ok": {
                          const { value } = result;
                          return Ok({ value });
                      }
                  }
              }
              

              Note the duplicate Err functions generated in TypeScript there.

              1. 3

                This should be fixed now!

                1. 3

                  Yeah there’s no checking of collisions or anything at the moment. It’s on my todo-list.

                  1. 2

                    Keep at it, good luck!

        2. 1

          I am curious what the goal is with this. Typescript is compiled to javascript before running in production anyway, so is this intended as a compatibility step for using Elm-like code in TS projects?

          1. 10

            I’m not a part of this project but having coded in Elm and PureScript for years and very recently touching TypeScript, TypeScript does not meet expectations for ergonomics for functional programming and is unreadable in comparison—even if the type system is sufficient with a few flags.

            Elm really pulled some poor design decisions IMO, that sealed it’s future as niche-only, and it seems quite a few projects outgrew Elm’s limitations. PureScript, as powerful and pretty to write as it is and access to multiple back-ends, goes beyond the simplicity of Elm in what I experienced as a loss in productivity either in wrapping one’s head around some advanced types or needing to flesh out missing pieces in the ecosystem because the community is much smaller. TypeScript ecosystem is massive (albeit very hit-and-miss on quality and definitely leans imperative and OO). While they can consume the larger JavaScript ecosystem, neither Elm nor PureScript can easily consume TypeScript type definitons or general code without first compiling it. I assume this is derw’s niche, bootstrap on top of the TypeScript with ML-style ergonomics, migrate frustrating Elm apps, and leverage the TypeScript ecosystem which has in some senses outgrown JavaScript. Assuming derw has managed IO, it has a niche over ReScript as well.

            1. 3

              Elm really pulled some poor design decisions

              I’d love to hear more about them (in a separate blogpost or similar).

              1. 10

                This blog entry echoes a lot of my same feelings–and also links out to some comments in threads I had left.

                Summary of my own beefs:

                • ports are limiting (can’t send files or other ‘useful’ types through them, and can’t be synchronous)
                • necessary FFI code can no longer be written (need a browser API that’s synchronous? Tough luck)
                • Web Components are a band-aid for stuff Elm just can’t do: can’t make it work in Elm, go stateful
                • glacial update pace to the core and compiler with open merge requests dating years back with bug and security fixes
                • no meaningful i18n or l10n story (would not recommend on this alone)
                • community has pedestals and a select few get a lot of power
                • overly beginner-friendly community, in a way that mentioning ‘monad’ can be considered bad and as such people end up writing their own, but less good version (e.g. bind without pure) and without the theory or understanding what andThen should do (type classes would be nice, but understandably bring baggage)
                • Optics, despite simplicity, frowned upon leading to ugly nested update code or making your entire state flat which leads to naming issues and just isn’t well organized
                • no good way to communicate between components leading to the one of the simplest solution (IME) being to pass the whole state into every component and use Optics to update the parts of state you need
                • browser application API breaks a lot of tools and browser add-ons; community suggests mounting to an element and manually hooking up location listeners that the browser application was supposed to simplify and do for you
                • packages cannot be consumed without publishing (say awaiting a merge request upstream) nor can they be in private repos; you see some people publish forks to get code out the door instead of wait and since there’s no unpublish, you can’t remove it once your merge is complete leading to package rot and confusion; vendoring is a bad idea and could go against the package license
                • can’t reasonably publish updates for prior versions of Elm
                • compiler strips license info
                • identity and package system is tied to Microsoft’s GitHub accounts so all community members must create an account and agree to a closed-source, private company’s ToS as a bare minimum participate in an open source community
                1. 1

                  Thank you so much ♥

                  1. 1
                    1. 2

                      There’s a place for both. The names can be goofy or confusing (or even ‘wrong’), but the ideas are sound and people miss out on shoulders of giants they could be standing on.

              2. 2

                Or vice-versa, if you want access to the typescript ecosystem from an elm-like language.

                1. 1

                  I have a blog post in progress that will summarize the goals, but basically: I want a way to write ML-style code that works with TypeScript (and generates TS as an escape hatch), so that I can use it at work.

                2. 1

                  Coed looks interesting. Elm architecture, but in typescript.

                  1. 2

                    eeue56 did a lot in the Elm community. I will venture to guess one of two things happened here: 1) a new project or its lead decided on TypeScript and TEA is still one decent way to construct UI architectures and it was ported to TS to use a familiar, tested pattern or 2) a project hit the limitations of Elm and was forced to use something else with types (which currently usually involves migrating to TypeScript, ReScript, or PureScript). Seeing the dates of these projects leads me to believe the author misses the ergonomics of an ML language but needs to be inside the TypeScript ecosystem.

                    1. 1

                      Somewhat accurate - I find myself using TS a bunch for work, but find working with React frustrating and find the syntax of TS to be awkward for type-safe code.

                      1. 2

                        Did you look at Rescript (bucklescript) at all? What puts me off is the emphasis on React, it’s not clear there is a decent TEA implementation for it.

                        1. 3

                          Yep, I did. Not a fan of the OCaml styling, plus my experience with the toolchain has been pretty rough - though I know they’ve improved stuff a bunch since I last used it. In my eyes, Elm is near perfect syntax wise for my personal preferences. I just want a better way of working with TypeScript projects with it.

                        2. 1

                          Thanks for reaffirming my understanding of the state of these technologies. I can definitely understand the niche you’re filling. I can empathize being unhappy with TypeScript ergonomics as well specifically moving from a PureScript project to a new, different TypeScript project.