Please log in to watch this conference skillscast.
The F# language delivers practical, enjoyable, and productive programming for the era of the cloud. At the core of F# is succinct, performant functional-first programming, compiling to both .NET and Javascript, with cross-platform, open-source toolchains for those at home in either ecosystem.
In this talk I’ll describe how in F# 5.0 and beyond we are adding more magic right across the F# stack – keeping programming simple and correct yet delivering the features you need for maximum productivity:
- Added expressivity and performance for DSLs using F# computation expressions
- High-performance state machines and resumable code for functional DSLs for collections, tasks, asynchronous sequences, and more
- Improved package management integration in F# scripting
- Interactive notebooks and a wide range of other tooling improvements
- F# analyzers, e.g. for additional shape checking in AI tensor programming
- Turnkey programming stacks for the client, server, and full-stack programming
Join me for this walk through the latest in 2021 for F#
Q&A
Question: What extra type level features would you like to see in F#? Specifically things that you think are considered advanced but would really help solve practical problems.
In my work I’d like to use more GADTs (I don’t think F# has that), and the new work on effect systems/ linear types and more dependent typing.
Answer: This is a good question. F# is bounded partly by interop concerns - I think a little more than some other languages - we suck on a lot of .NET libraries directly, which is great from many perspectives. But if the libraries don't come with sufficient effect information then we don't gain that much by adding effect tracking.
That said, effect systems would work beautifully with F# computation expressions.
So it will be an area we will be keeping an eye on.
Question: What is F#'s ad-hoc overloading mechanism and how does it work?
Answer: There are two.
First, F# resolves object model overloading using nominal type information and
type annotations inferred on a left-to-right basis. This means, for example,
let f (x: int list) = x.Length
resolves . But that doesn't offer
generalization.
So F# also has a mechanism called "SRTP" (Statically Resolved Type Parameters) that allows structural constraints based on operations. These are generalized for "inline" code.
For example
let inline double x = x + x
resolves to a generic "double" function usable with any type supporting the "+" operation. This gives the basic kind of entry-level thing often solved with type classes.
However, while SRTP is technically quite powerful (see the FSharpPlus library for example), we make writing SRTP code kind of hard, somewhat on purpose. This is partly because I don't believe the Fucntor/Monoid/Applicative/Bifunctor kinds of category theory characterizations that people build are really hitting the sweetspot in coding simplicity. And also SRTP has a few glitches. And finally we may end up aligning with combined F#/C# proposals to add other variations on type-level programming.
Question: How do computational expressions improve on haskell "do" syntax?
Answer: This is covered quite a lot in "The Early History of F#" paper. https://fsharp.org/history/hopl-final/hopl-fsharp.pdf
Firstly, F# computations expressions are a single feature covering both comprehensions and monads (and other things). So in a sense it plays the role of two features of Haskell.
Next, F# list computation expressions support appending in nested position (with variables in scope). They also support the entire control syntax (try-catch, match, try-finally, nested function definitions and so on). In practice my impression is people write much larger and richer list comprehensions in F# as a result - I'm confident that would be borne out by a study. Entire applications are written where the view is basically one big comprehension.
Next, F# the notation in computation expressions more generally can be extended through "custom operators". This was actually inspired by Haskell-related research by Peyton Jones and Wadler ("Comprehensive Comprehensions").
Finally, F# computation expressions can also support query operations including LINQ-style meta-programming.
Check out The Early History of F# for more details on most of this, it's been reviewed by Haskell folk so should help clarify.
Question: What’s the current thinking around
FSharpAsync
vs Task
- are we likely to see async
formally deprecated & replaced by a builtin equivalent of TaskBuilder?
Answer: No, F# async won't be deprecated (we may try to reimplement it in a backward-compatible way on more efficient foundation)
Some of this is covered in the F# Async Programming Model paper.
- F# async offers implicit cancellation token passing. This is really much nicer than with tasks
- F# async allows safe async tailcalls
return! someAsync
- F# asyncs are composed, then started. There is no implicit start like there is with tasks. Cognitively this is nicer to program with.
That said, I'd expect to see task { .. }
gradually become the norm,
and likely the first thing taught.
Question: Thanks! I’ve mostly been converting tasks to async,
but I’m thinking if you’re predominantly writing application code that just runs
tasks from C# dependencies you may be better off using task { }
directly.
Answer: Yes, that's a fine approach. Also task performance is better for the async primitives, though in practice none of that normally matters if there's even a single asynchronous I/O (as there usually is)
Question: It's weird to see the error type on the right in result. F# doesn't have partial type parameter application?
Answer: Yes, F# doesn't let you partially apply type parameters.
Question: F#'s approach to simplicity has a lot to offer many disciplines. If you were to increase uptake of F# in one domain which domain would you focus on: C# devs, data scientists, data engineers, web devs.
Answer: Hmmm good question. It would be fairly evenly balanced between all four I think.
More generally, I would say "Python programmers". It's clear that very many people are being sucked into a sub-optimal point with having Python as their one and only programming skill. That's fine up to 500-5000 lines but soon they will need more. The succinctness of F# plays very well with the Python mindset, yet delivering performance and strong typing.
Question: F# is influencing C#, but what efforts are in place to move to using more F# in .NET itself? Is that even a sensible idea?
Answer: In practice, no. For core .NET components the dependency on FSharp.Core alone is enough to make people do the rewrite to C#, even if they prototype in F#.
Question: I have to say, as a Haskeller, fsharp makes me
jealous. My brain doesn't work without monads and typeclasses, but fsharp has a
very nice and coherent design. I wonder what Haskell can learn from fsharp's
design (other than (|>) = flip ($)
)
Answer: I think in practice Haskell can learn a lot from F#, yes, and its an under-explored area ripe for some student or hacker language-designer to just let rip on in an iconoclastic kind of way.
Some specific areas that come to mind:
- Haskell's do notation could be thrown away in favour of something more like computation expressions. This would include allowing much more imperative looking syntax (try/finally, try/with, for, while) inside the notation, maybe to the point of basically having C or Java or C#-style control construct syntax
- Personally I think nominal object programming with implicit object constructors (even without any inheritance) is really nice even for purely functional data. A version of this should be incorporated, plus a monadic version to allow a monadic computation as the implicit constructor
- F# integrates subtyping into HM type inference and nominal object programming well enough for practical purposes. The ways it does this won't satisfy FP purists (it's nominal and has cases where it's left-to-right algorithmic) but it's good enough for practical object programming
So basically, to be iconoclastic, someone could experiment with sort of throwing away Haskell as it stands today and make a Haskell* that embraces both richer syntax for monads and also a degree of nominal object programming, while keeping the core expression language.
The problem is, that would split the Haskell community in all sorts of ways, and not only because it's effectively a new language. It might satisfy those who just want to get on with monadic/imperative/async/nominal-object programming in a practical get-stuff-done way, but certainly wouldn't satisfy those who need none or few of those things and indeed have strong dislike for them, and don't want Java control constructs emerging in the middle of their language. (Core OCaml and F# steer a real balance between having control constructs but still basically being functional-first programming). There will also be interactions with laziness I haven't thought of, especially in the object notations.
The Haskell community work hard to maintain its unity and I actually don't see this being addressable without a new language, just like Scala, F#, Swift, Kotlin etc. have all been new languages.
YOU MAY ALSO LIKE:
What’s new in F# 5.0 and beyond
Don Syme
Principal Researcher
Microsoft Research, Cambridge