Skip to content
Yawar Amin edited this page Nov 25, 2017 · 8 revisions

An Outsider's Guide to Statically Typed Functional Programming begins by teaching Elm and its idioms. But Elm is an intentionally limited language. A question I should answer soon is: what language should be used in the rest of the book?

How about F#?


Leaving aside the object/procedural parts of F# -- treating it as a pure functional language -- it adds the following to Elm:

  • Active patterns
  • Units of measure (vs. phantom types)
  • Macros/metaprogramming
  • Type providers
  • Computation expressions as a variant of monads
  • and...?

How robust and usable is Fable compared to Elm?

F# vs. something like Haskell and PureScript:

  • How much educational value is lost by going with F# rather than being explicit about monads, IO, effects, and all that?
  • What's lost without type classes?
    • How usable is fsharp-typeclasses in production? as a tool for learning about type classes? (I'm not shy about explaining the "what" of a feature by diving into the "how".)
    • Does it facilitate the "Aha! what I need here is a Foldable" style of development? (Alternately: "Aha! This is almost a Foldable. I might as well implement all the required functions to get all the rest of them for free.")
    • Is there tradition/support for developing coherent laws involving multiple functions as a way of modeling a tractable piece of a domain?
  • ... and what else would someone knowing only Elm and F# be missing?

What else should I consider as I decide?


S.W. Nov 24

Lots of good questions! IMHO for teaching FP to beginners, what's important is "functions as things", composition everywhere, and algebraic data types. Everything else is secondary. No need for type classes, etc. Both Elm and F# are perfectly good choices for that.

The problem with too much focus on purity and IO is that you have to know about monads before you can do "hello world" (which shows up in Ch 9 in "Learn You a Haskell"!). Fine for extending your toolkit but I think a limited language is better for learning.


Mark Seemann, November 25

First of all, I agree with what Scott wrote.

F# would be a nice next language after Elm. I'm not too familiar with Elm, but what I've seen looks as though the learning curve from Elm to F# would be appropriate.

Neither Haskell nor PureScript would be a good next language, I believe, because the learning curve would be too steep. The syntax is also different enough that it could confuse readers. I write this although I really like Haskell, and what I've seen of PureScript has been very to my taste as well.

If I may offer a bit of concern about the choice of F#, I wonder how readers not on-board with Microsoft would react to it. These days, it's a multi-platform language at least in theory, but I don't know how well it works in practice. I've been working professionally on the Microsoft platform for some 20 years, so I don't know how F# works on Mac or Linux, but even on Windows, the F# development experience is a bit rough around the edges compared to what C# developers are used to.

Still, I can't recommend any better language. It's not that the development experience with Haskell is particularly smooth, and what I've tried with Clojure and Erlang, those aren't even contenders when it comes to tooling...

Specifically about F#, I think the most important bullet points would be active patterns, type providers, and computation expressions.

Units of measure is a language feature that's often put on display, but I've never used it. Whenever I ask other F# developers, they haven't, either.

What do you mean by Macros/metaprogramming? I'm not aware that one can write macros in F#. Metaprogramming is possible via .NET Reflection, but I've found less use for it in F# than in C#, since F# is a more expressive language it itself. What I have in mind is that most Reflection I've seen in .NET revolves around DI Containers and Mocks and Stubs, and I'd assert that a well-designed Functional code base has need of none of those.

Type providers are nice for generating types from XML or JSON examples. Apart from that, I don't use them much. For database development, for example, they need a connection to a live database in order to work, which makes it difficult to do iterative, outside-in development. For XML and JSON, though, type providers are awesome!

Ultimately, I think, you'll have to talk about monads. You can't understand computation expressions without understanding monads, and I don't think you can properly separate concerns in functional programming if you can't stack monads (one for async workflows (Async), and one for error handling (Either or Maybe) - that's the most common monad stack in F#).

Stacking monads is a bit easier in Haskell because of typeclasses and monad transformers, but you can get by just fine in F# without typeclasses. Functor and monad are mathematical abstractions from category theory, and they exists regardless of language support. In F#, both Option, List, Async, and so on are functors and monads. There's no explicit way to declare them as such in the language, but they all have the appropriate affordances, and they obey the functor and monad laws, so are therefore monads.

Still, F# computation expressions aren't 'a variant of monads'. Computation expressions afford syntactic sugar for monads, just like Haskell do notation does. Yet again, because of the lack of type classes in F#, you don't get do notation for free in F#; you have to explicitly write a computation expression builder in order to get that. For anyone who understands what a monad is, however, doing so is trivial.

Ultimately, I believe that understanding how to separate pure and impure code is what'll make or break the long-term sustainability of a functional code base, but I agree with Scott that it does require that one first understands monads. It took me years until I finally got my head around the IO monad...

All the above has been a bit of a brain dump, so I don't know if it's helpful. You probably know a lot of it already. In any case, if I can be of further assistance in this regards, please reach out.


@yawaramin, 2017-11-25

I think the key point here, one that you brought up first, is that Elm is an intentionally simplified language targeted almost as a DSL for writing webapps. The next step after that is when you try to scale up and modularise your app, you need something that abstracts the same operations over different types. There are three main approaches to this: object-oriented with inheritance polymorphism, typeclasses, and modules. I think you realise the problems with the first approach, so I won't discuss it here. The remaining two approaches are the really interesting ones because of their power and flexibility.

In this discussion I'm assuming you're only interested in compile-to-JavaScript languages.

If you subscribe to the typeclasses approach (compiler should infer the return type of the operation from its context), then the best choice is PureScript. F# doesn't support typeclasses (although it may in future); the fsharp-typeclasses aprpoach I demonstrated is a manual way of doing typeclasses that loses the benefit of the compiler helping you. Plus, as you know, PureScript has higher-kinded types and can express useful things like Functor.

If you subscribe to the modules approach (statically create and use modules to tell the compiler the return types of operations), the best choice is BuckleScript (if you want to stay with traditional ML syntax) or ReasonML (if you want to show a more JavaScript-like syntax and JSX integration).

Personally I like the latter because it's more explicit and I feel more powerful. Plus I really like how fast the BuckleScript compiler is and how nice the editor support is. But, I believe either choice would be great.