A really interesting and honest write up by Don Syme on the goals and trade-offs required to get a functional language implemented on .Net, in pre-open Microsoft.
I use F# for anything critical and/or complex. It's a parsimonious way to represent domain models and a clear way to express computation.
My main regret with F# is that I didn't get into ML languages earlier in my career.
BTW a recent book, 'Stylish F#', is the best introduction to F# so far (no affiliation, just bought a lot of F# books over the years).
Author of 'Stylish F#' checking in. Thanks for the endorsement!
Incidentally 'Stylish F#' is intended to be an intermediate book so it might be worth reading Chris Smith's 'Programming F#' or one of the other intro books first - or some beginner online material. That said if you had C# exposure and were determined, you could probably get away with using 'Stylish F#' as your intro.
Thank you for the book recommendation, I've been looking at F# as a new language for a while but there are quite a few books and it's sometimes hard to figure the wheat from the chaff.
I've been slowly migrating from C# to F# for our automation assistant (https://lexico.io - not yet usable yet but will be soon), and like the top comment here, my only regret is that I didn't do so sooner.
Functional languages allow logic to be expressed so precisely and concisely. I hazard a guess that every F# module is 2/3 the LOC of its C# equivalent, with the added advantage that everything is incorporated into a single file.
F# certainly has its quirks (as a language, I think Clojure is actually cleaner - but that comes with the downsides of the JVM and .NET interop is a huge advantage).
Part of me doesn't want to spread it around too much, because I feel like I've discovered some hidden edge over my hypothetical competitors.
I share the same regret. In fact, by far my biggest regret in my career was missing an opportunity in 2008 to switch to F#. The portfolio management team I was with departed a large hedge fund to start our own. I knew I had 8-10 months before we started trading again. It was every programmer's dream: hit the pause button and get a chance to leisurely refactor everything you've never had time for. Even enough time to completely change tool sets. I remember discovering F# during this period and it was as though Don Syme (the F# BDFL) had listened to all my complaints about C# & Matlab and built a language specifically for me. Ultimately, I chickened out. I was afraid Microsoft would eventually Foxpro F#; or I'd hire people who'd tell the CEO of our firm that I was nuts and had built all our systems in an obscure language. By late 2012 the ecosystem had matured tremendously and I finally switched to F#. I really wish I had those four years back. F# would have saved me a ton of while-on-vacation troubleshooting from 2008 to 2012.
We are currently rewriting Buckaroo in F# (https://github.com/LoopPerfect/buckaroo/tree/buckaroo-redux ); previous versions were in modern Java. The productivity gains are tremendous... FParsec, async, asyncSeq, records, match expressions are all a huge leap over what most OOP languages offer. Deployments are also much easier thanks to Core RT.
1) The type system is focused on mathematical types as opposed to compiler tags. Mathematical types are about ensuring that a function will not fail. Compiler tags are about ensuring that you generate the correct asm op codes. Mathematical type systems allow you to have an easier time knowing the code works.
2) Algebraic data types provide both an AND component and an OR component. That is, you can do type X = A of int * int * int; AND you can do type X = A of int | B of char;. C# style languages provide you with the AND component (class A { public int B => ...; public int C => ...; }). However, they don't provide the OR component. You can create it by constructing weird type hierarchies, but this isn't idiomatic AND it also isn't closed. With an algebraic data type you can know that all of the cases are handled in a match. This isn't something you can ensure in C# (at least not naturally or easily).
Ultimately, I don't think functional programming provides anything that has to be better than object oriented programming. It's just that there are two facilities that tend to go with functional programming that allows a better way to model the problems that we encounter.
Missing from your list is arguably the most important difference: programming with mathematical (immutable) values. This allows one to build very high-level abstractions and easily reason about correctness. OOP languages simply were not designed for this style of programming.
There are also many other significant differences. I estimate I am about ten times more productive in Haskell than Java.
I don't think mutability is problematic. The problem with mutability is that you can create hidden communication channels between different sections of your code and it can be very difficult to determine what is actually going on without carefully analyzing the entire code base at compile time and at run time for every possible input.
So if you only have mutability inside of a function and it cannot escape, then you have no problems. Or if you have a way to prevent it from crossing module boundaries.
Rust for example, gives you some pretty good tools for controlling mutability and for tracking it. I think this is actually superior to a fully immutable system as with full immutability you end up with other weird problems (like needing monads and monad transformers to do things that would otherwise be simple).
Sure, there's nothing wrong with pure functions that encapsulate mutation, even Haskell supports this, but this isn't how OOP typically works. OOP developers actually use mutation to model the real world, for example a bank balance! Many of their APIs and libraries are designed around mutation, even the default containers. Java got String right though (apart from the UTF16 part).
Strong agree on the "but this isn't how OOP typically works" part.
I think one of the reasons that Rust doesn't have a traditional OO model is because you can't actually enforce mutability controls in that way. At the very least, when I tried to think of how I would find a way to enforce mutable and immutable data using java / c# style OO, I couldn't think of a way that wasn't kind of crazy or hard to use. On the other hand, structs with traits is actually pretty easy to enforce immutable data (hey, this requires a mutable borrow and what you have is an immutable borrow, so compile error).
My position is this: different programming languages have different strengths and weaknesses. C++ is crazy fast, but it lacks many high-level features (most notably do-notation). Efficiency and performance are its niche. Conversely, F# (or any ML) is very good at expressing complex control flow, but performance is a secondary concern.
Package management is an IO-bound SAT problem, so we are using the best tools for the job.
ML could be as fast as C++ and even have manual memory management. Take a look at mlton [1] and manual memory management [2]. Most ml compilers just value compile time more.
Maybe the world needs a language that is like clasp is to lisp and c++, but for c++ and ml?
Curious about your deployment happiness - are you bundling the runtime with your program? Last I checked, such a bundle was large (compared to a static c++ binary), and didn't have a nice single/few file(s) "container"?
Our development process is very pleasant. We use VS Code, .NET Core and Ionide as an IDE. I find an IDE essential for languages with global type-inference.
For bundling, we use Core RT to create a self-contained application. This is then bundled using Warp (https://github.com/dgiagio/warp) to generate a single binary.
Thank you. If I read that correctly, the last line runs your artifact - is that for CI or something? (I'd expect the purpose was to just create the artifact?)
Ed: then again, your artifact is a build tool - does that build distribution packages?
Yes, the last line is for CI. It just runs the tool as a simple check that it built correctly. Cross-compilation does not work with Core RT anyway, so we can assume that you are always building for the host platform.
I remember I had a big fight with dot.net a year or two back, and gave up - but last time I checked the only thing I found missing was a bundler (like warp). And somewhat inconsistent/unclear documentation on how to make a stand alone build. But I did eventually manage to build "Hello, world!" on Linux and run it on windows as a proof of concept.
F# has the power of .NET and its libraries behind it.
Haskell's insistence on purity and laziness does increase the time it takes to design things well. F# makes it easier to violate purity in controlled ways, and is not lazy be default.
People who've used both say it's a lot easier to get lost in abstractions with Haskell, and there doesn't appear to be a clear "stopping" point. A common statement by people who move from Haskell to OCaml/F# is "I spent N years on Haskell but didn't produce much. In 6 months of (OCaml|F#) I've produced more than in those N years with Haskell."
The bottom line: The .NET libraries and slight relaxing of purity/laziness allows for more productivity.
(I'm not an expert on either - just repeating what you normally find in the threads).
The main reason was that team had more experience with F#. I think it is also more approachable than Haskell for OOP devs (C# to F# comparisons are quite common on the internet). I'm sure Haskell would also have been an excellent choice though :)
f# is a multi-paradigm language. it allows you choose what paradigm fits the problem best and allows you to easily mix paradigms, and it does so in a clean way. haskell does not have this flexibility. this is f#'s power.
The scala toolset is incredibly crap though. Ask the engineers on my team about the huge pains of transitioning to using IntelliJ from Visual Studio, or the joy of trying to make SBT do what we want it to do, then trying to use Maven instead.
Any new engineer we get started with Scala has to spend about a week making their local setup work. It's a total pain in the arse.
SBT is awful (so ignore it); Maven is wonderful. Visual Studio is very good, but grandparent said their code was previously in Java, in which case their team will already be used to IntelliJ or Eclipse, and it's F# that would involve making a transition.
We wanted to make significant changes, so a gradual migration would not have been possible regardless of language. Regarding Scala vs F#, I do not know enough Scala to comment. Perhaps someone else can weigh in?
I've spent a fair bit of time with both. Personally, I don't find the two to be all that comparable -- they're fairly distant cousins.
F# tends to stick much more closely to its ML roots, and fairly scrupulously retains a more ML flavor except when necessary to achieve good interop with the .NET ecosystem.
Scala, on the other hand feels less to me like a dialect of ML and more like an ML-flavored object-oriented language, because it mixes in OOP-style features much more freely. Its algebraic data types, for example, are explicitly implemented as class clusters. A lot of people like this, others see it as an anti-feature. Where you fall probably depends on how you feel about the relative merits of OOP and FP.
It's also, for better or worse, less concerned with maintaining good interop with the rest of the ecosystem. Consuming F# modules from C# isn't always pretty, but it's always possible. There will be C#-friendly wrappers, but creating them is a matter of aesthetics rather than necessity. Consuming Scala modules from Java, though, frequently requires creating wrapper libraries. On the upside, this does mean that Scala gets to have things like typeclasses, which the F# team has kept out of the language due to concern about how they would impact interoperability with the rest of .NET.
Last but not least: F# has type providers. Scala has implicits. F# has quotations. Scala has more stuff that's also called implicits.
“Since around 2007 strongly-typed functional programming has shifted from relative obscurity to be a central paradigm in programming. C#, Java, C++, Scala, Kotlin, Swift, Rust and TypeScript now all include elements of strongly-typed FP, and Apple executives extolled functional features at the launch of Swift in 2014, including pattern matching, generics, option types, type inference, tuples and closures, something unthinkable in 2005.93 Haskell, F# and OCaml have all grown in use, and newcomers such as Elm and ReasonML are also finding good adoption.”
I feel like we’ve taken the long way to popularize functional programming.
As someone that spends most of my work time on .NET, it would be nice if the .NET team would take F# into consideration in some of their design and tooling decisions, instead of looking just into VB.NET and C#.
As it is, F# is like the cousin that doesn't always get the party invitations, while JavaScript, Python and C++ get the them, even if they don't belong to the club originally.
I think Scala's pattern matching is pretty cool too. I was surprised by how weak Haskell's pattern matching is by comparison (by default, I don't yet know of any GHC extensions that could help).
That said F# and Scala aren't really competitors. The real competitors are the mainstream languages, like JavaScript, Java, C# and Python and that's what their marketing should target.
This creates a bidirectional pattern synonym - you can both match and construct values with it:
foo :: Cons' s Char => s -> s
foo (a :< b) = 'c' :< b
foo other = other
Pattern synonyms make completeness checking undecidable so you need to give manual hints to get a nice api. It also wreaks cost models - is foo O(1) or O(n)?
For anyone interested in how some of the modern history is shaping up from a language perspective, check out the Language RFC repo: https://github.com/fsharp/fslang-design/
I think that the early expectation was that, since (non-.NET) Visual Basic was being sunsetted at the same time, VB6 developers would quickly adopt VB.NET, and that would lead to VB quickly becoming the most popular .NET language.
Which, yeah, proved to be hilariously wrong. Here we are in 2019, and I can still name several actively maintained applications that are written in VB6.
Another thing they couldn't have really known at the time was how exactly C# and .NET would evolve over the following decade. Once upon a time, interacting with COM interfaces was much easier in VB.NET than in C#, and that made it relatively more attractive as an enterprise dev language. C# 4 more-or-less closed that gap -- VB.NET's other practical advantages are so minor they don't really even bear mentioning.
My recollection is that .NET was largely a research project within Microsoft around 1997. It was when ScottGu used .NET to build ASP.NET (circumventing many limitations of Classic ASP that he also helped build) that .NET took off as a viable commercial platform ushering in further development and extended language support including F#.
Excellent article. Kudos to the authors. I look forward to digging into your references.
This would be a great primer for young developers to get a good overview of of programing paradigms and modern OS language evolution over the last 25 years. I will be sending this along to all my peers.
Also, I am impressed with .NET and Microsoft's embrace of OpenSource. I am one of those developers who got stung by Microsoft in their earlier days when they were the true Evilcorp and now they really seem to be less evil with this embrace.
I will actually consider using F# for my future projects, but of course as long as it runs 100% on *Nix platforms.
I use F# for anything critical and/or complex. It's a parsimonious way to represent domain models and a clear way to express computation.
My main regret with F# is that I didn't get into ML languages earlier in my career.
BTW a recent book, 'Stylish F#', is the best introduction to F# so far (no affiliation, just bought a lot of F# books over the years).
https://www.apress.com/gp/book/9781484239995