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

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

      2. 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 :)