1. 3

    There are very limited options for saying “I’ll do this part later”… You application has to compile.

    I think there are many good things in Elm, but I do find this a bit frustrating, given that there are tools available to address this for statically typed languages, like typed holes and deferred type errors, that can reduce the amount of friction during development while not compromising on correctness.

    1. 3

      PureScript breaking out to FFI was an easy way to hack around this for now and clean up or upstream later. The best “hacks” in my experience involved things along the lines of using globalThis.Intl, instantiating an Intl formatter in the FFI file with it’s options and then just consuming IntlObj.format as a pure, synchronous function to get quick date/number formats.

      1. 2

        Usually when I write Elm, I start with the types and let the type errors guide me to what I want to achieve. So I guess the workflow is types => base functions => actual usage in update/view. I have a similar workflow in Derw, but it’s actually possible to work around that currently by calling some global function since they aren’t type checked. So you could have

        isNumber: string -> boolean
        isNumber str = 
          globalThis.doNothing
        

        and replace globalThis.doNothing once you’ve figured out what goes there.

        1. 1

          Does Elm not have the equivalent of Haskell’s undefined?

          1. 1

            It has Debug.todo "some message" which you can use similarly. The difference is you can’t use any Debug functions in prod builds. I use them all the time when I’m blocking out my code, and then remove then 1 at a time till I get what I need.

      1. 1

        Work:

        Published an article about what we got up to last year.

        Personal:

        Thinking about how to implement side effects in Derw. I have a rough idea for an API, which will function similarly to async/await. But I’m debating adding do-blocks ala Haskell. In Elm, client-side, you rarely need to chain tasks - which means you don’t need special syntax for working with them. But in my exploration with server-side Elm, I found that I was chaining tasks a lot. For example, loading a user from a database and then doing something with that data. So it might make sense to have a do block, where every const is evaluated via await.

        This would require kernel code to wrap non promise/async code, but that’s alright for a first attempt I think.

        1. 5

          Company: Aftenposten

          Company site: https://ap.no

          Position(s): Junior, mid, senior developers

          Location: ONSITE Oslo, Norway

          Description: Aftenposten is the biggest subscription news service in Norway. We’re growing to take on new challenges and technology, including things like improving our news feed to provide more valuable information to readers, provide a better news experience in cities like Oslo, machine learning powered news and audio. We’re looking for devs of all experience levels to join us.

          Tech stack: TypeScript, React, Handlebars, Varnish. There is some freedom on what tech we use - so smaller side projects might use other tooling/

          Compensation: Typical Norwegian salary. There’s a shares scheme too with our parent company, Schibsted. We invest a lot into our developers and are eager to see people grow

          Contact: Either me at noah.hall@schibsted.com, or the job post

          1. 1

            Compensation: Typical Norwegian salary.

            It might not be Silicon Valley level, but by this-part-of-the-world standards quite good. And a housing market to match. :-)

            1. 4

              Yep, it’s definitely a comfortable life. Working with the most free press in the world, in one of the happiest countries in the world. I should also mention we are willing to help people with visa sponsorships.

          1. 1

            I’ve implemented Coed support in Derw. That basically means that now most Elm code should work with minimal changes, outside of some interop stuff (like Http). My monthly status post already has enough content to be published, but I think I’m going to try to wait to the end of the month til I have really have a ton to share and filter out what’s important.

            At work, focus is on hiring.

            1. 2

              Nice work switching to a lexer!

              1. 2

                Thanks! It was a good idea, I was just being lazy so I could focus on other parts. But properly lexing has helped me a bunch chasing down edge cases.

              1. 1

                For work, I’m writing up what our dev team did over the last year to give perspective hires some info on what our worklife is like. I work for Norway’s biggest subscription newspaper, and our big highlights: redesign of the frontpage to provide users with more context and increase time well spent when browsing us. Work with an ML trained version of our podcast journalists which will provide text-to-speech for all articles. Using AI to generate real estate trend articles. And a stream dedicated to working with innovation in a lean manner. We also spent one day every fortnight working on self-improvement, ranging from ML, Git, React, language development. If that sounds compelling to you, we’re hiring people of all levels in the job add here. Feel free to mention Lobsters in your cover letter.

                In my private time, I’m working away on Derw. I have a monthly blog post coming up going over all the major changes, along with some thoughts and reflections on language design. I’m getting very close to the point where I can start my first big project in Derw itself: a homepage.

                1. 1

                  One of the weak parts of Elm, compared to other MLs, is the lack of typeclasses or traits for organizing generic code. Will Derw eventually have a feature like this?

                  1. 2

                    Not in Derw itself, but it might be possible to write generic code in TypeScript and use that from Derw. It depends a lot on what I can get out of the TypeScript libraries, and I’ve only spent a little bit of time digging into that so far. But will you be able to write typeclasses in Derw itself? Nope.

                    1. 2

                      What’s the reasoning to exclude them?

                      1. 1

                        Similar reason to Elm - in my experience, they aren’t needed to write productive or good code. There are alternatives out there that have typeclasses already, like PureScript, and they solve that need quite well. Derw is intended to be a simple language, as close to Elm as possible.

                        1. 1

                          That’s interesting. I find it impossible to write statically typed code productively without typeclasses. I always thought they wanted to add it to Elm but just never agreed on a design.

                  1. 1

                    I share the two really big sentiments from this article:

                    1. TypeScript is a marked improvement over vanilla JavaScript, but is not particularly FP-friendly. The fact that the designers still can’t quite make their minds up between interfaces and type aliases appears to reflect two competing design goals: a) to make JavaScript more like C#, and b) embracing JavaScript’s functional roots in Scheme by supplanting C#-like features like enums with union types, which are almost algebraic data types.
                    2. I wish I could write Elm and compile it to TypeScript (or JavaScript with a type declaration file) so it can be used directly, beyond the DOM. I greatly appreciate what Elm’s authors and maintainers have accomplished in making the ML language family approachable. But I find it ironic that Elm’s greatest strength is its ability to write very robust library code that compiles to JavaScript and yet one can’t actually use the Elm libraries outside of Elm because the compiled JavaScript operates directly on the DOM.

                    My question isn’t so much “Why not TypeScript?” or “Why not Elm?” as it is “Why not PureScript?” This isn’t a rhetorical question as my familiarity with PureScript is limited to a few minutes of noodling around and poking through the docs. Perhaps you’ve found some shortcoming or conflicting design goals I don’t yet appreciate.

                    Also, I’m curious if you intend to implement TCO in your compiler or if you plan on leaving that to the JavaScript engine in the hopes that eventually TCO will return to V8.

                    1. 3

                      I hit on 1 a lot with my colleagues and friends. There’s so much that TypeScript gets right, but if you’ve written FP in a language designed for it, it’s a completely different experience. There are some TS libraries out there than enable FP-approaches, but I find them often come at the cost of making error messages way more complicated, or the code itself more magical than I like.

                      For 2, that’s kinda my position. I love Elm, considering I was part of the core team, and that hasn’t really changed. But the work environment I’m in, and the codebases I work with have. So now I need a way to work with TypeScript a little easier.

                      When it comes to PureScript, it’s a very powerful language. Lots of cool features which allow you to write powerfully type safe code. My problem is that one of the things I love most about Elm is it’s simplicity. The old Python adage of “there’s only one way to do it” is embodied by Elm, and that’s what I want. ML syntax alone won’t get me there - it’s got to have the tooling, the error messages, the simplicity. No diss to PureScript devs though! It’s just a different approach, and personal preferences.

                      TCO: Unlikely, but we’ll see when I get to the Derw rewrite of the compiler. It might be needed.

                      1. 2

                        There’s so much that TypeScript gets right, but if you’ve written FP in a language designed for it, it’s a completely different experience

                        As someone who writes TS without classes, but has never used a language designed for FP: Is there a way to explain what I’m missing without me just having to go and learn an FP-first language, to see for myself?

                        1. 5

                          My advice, generally, would be to just try out another language. Advent of code for example is a good chance to just try out a language you’ll never use again. But that might be from my perspective as a lover of languages.

                          It’s hard to distil exactly what a good FP language is. Some might say lists as a first class citizen, pattern matching, and recursion, for example being able to write:

                          sayHiToEveryone: List string -> string
                          sayHiToEveryone someList = 
                            case someList of 
                              [] -> ""
                              firstName::rest -> "Hi, " + firstName + (sayHiToEveryone rest)
                          

                          Some of the elegance of this approach is that you can write small, concise functions which typically have minimum amounts of nesting. Once you introduce imperative style variables, functions quickly can grow in complexity. It’s not always a given; you can write small functions in TS. But MLs or lisps tend to enforce this behaviour a little stricter (ignoring overuse of let/where blocks). Why are smaller functions better? Easier to unit/fuzzy test, easier to chain, easier to refactor. There’s also some aspects like the pipe operator which will come to JS one day, where it is very easy to chain function calls in a visually pleasing way.

                          One other aspect that Haskell/Elm lovers enjoy is purity. Functions can’t have side effects without being represented in the type system. That’s not possible in TS today. You can call any function from any other function, even if they have side effects (like modifying state, or resources). In Elm, these side-effects are wrapped up both in the MVU loop and represented in code as Tasks/Effects. This is my next big blocker with Derw: how does one represent functions in a pure language with no side effections, when external functions called may have side effects? There’s a few ways of solving that problem, but none I’ve landed on yet.

                          Back to the original question, though, how do I try FP without leaving typescript, you could probably check out some of the FP libraries. Ramda is a pretty comprehensive library with a lot of useful patterns. You could also look into things like immutable which force you to work with state in a way that doesn’t modify existing objects, but the replace them (i.e pure)

                      2. 2

                        The fact that the designers still can’t quite make their minds up between interfaces and type aliases

                        Do you know why both interface and type exist? To me this is one of the most confusing things in Typescript (apart from the “most useful info last” error messages!) and I’ve never understood why it has both. I did a quick Google, and mostly I found explanations of what the differences are, but not why both need to exist. For example, if we need interface for OOP, then can’t why can’t we do away with type? I have found them generally interchangeable, except that interface can be extended, so tends to be more useful to me.

                        1. 2

                          I have found them generally interchangeable, except that interface can be extended

                          Indeed they are mostly interchangeable. Even the exception of not being able to extend type aliases went away (I think in 4.0…?) so you can now do this:

                          type Jungle = {
                            continent: string
                          }
                          
                          interface Gorilla extends Jungle {
                            ...
                          }
                          

                          The main thing interfaces can’t do that type aliases can is union typing. I imagine there are other idiosyncratic differences, but this is the big one from an FP standpoint. Union types are a great way to take full advantage of the compiler to ensure correctness:

                          type Continent =
                            'africa' |
                            'asia' |
                            'europe' |
                            ...
                          
                          type Jungle = {
                            continent: Continent
                          }
                          

                          Union types are tantalizingly close to algebraic data types, which would make pattern matching possible. The TypeScript guide’s latest heuristic to “use interface until you need to use features from type” indicates that, by this point, type aliases have become a superset of interfaces. The only reason I can think of to use interfaces is to spare the compiler from the overhead of supporting type aliases’ additional features.

                          Do you know why both interface and type exist?

                          Getting back to your original question, no, I don’t. It strikes me as an accidental complexity that, perhaps appropriately, resembles JavaScript’s own conflicted origins.

                          1. 2

                            like struct and class :)

                        1. 2

                          I’m very supportive of the goal of seamless interoperability with the existing ecosystem. Kotlin is so amazingly easy to plug into a Java/Android project — it was a breeze to migrate our Android app over gradually as time allowed. I think to some extent Zig is following the same course with C - but I haven’t tried that out.

                          Plus, compiling out well-typed TS files means it’s easy to eject derw even at this early stage if (no offense) the maintainers decide to work on other things.

                          The one thing that looks odd to me is the import system. Looking at examples it seems like import fs is translated to import * as fs from ‘fs’? How do you map import paths? Any “magic” here makes me nervous since the TS ecosystem already had a bunch of import related weirdness going on: web bundlers usually add weird extensions, the browser vendors are adding import maps, real EcmaScript Modules in the bowser want to use real URLs, NodeJS is fighting an ESM vs CommonJS war in package.json fields, etc etc. Don’t even get me started on tsc’s own esModuleInterop and allowSyntheticDefaultImports… Anyways, my point is, don’t add to this pile of headaches if you can help it - any simplifying or papering over of these issues will come back to haunt your users later.

                          1. 2

                            Relative paths are done via strings, rather than a name.

                            import "./other"
                            import "./something" as banana
                            import "./another" exposing ( isTrue, isFalse )
                            

                            I only recently implemented this though, so I’m still thinking about it a bit. I might make it required that a relative import has to be aliased (i.e import "../banana" as banana). At the moment they get aliased by default to the file name - so import "./somedir/banana" becomes banana in the file’s namespace.

                          1. 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. 2

                                    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.

                              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!

                                    1. 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.