Is F# a functional language?

Vladimir Shchur
5 min readJun 25, 2024

--

F#mida with scales

The idea of this article emerged during the discussion of the future F# feature and some other discussions in F# chats, so I decided to give it a go. While historically F# was called “functional” language and then “functional first”, I feel that this is a bit misleading and wanted to give a practical overview of the features that can be regarded as “functional” or “somewhat functional”

Missing functional features

First and foremost I have to admit that FP zealots will immediately reject F# as a functional language because it currently lacks (and most probably will never have):

While those features are very important in FP languages for providing proper abstractions, F# developers live happily without them, because they have access to the OOP abstractions instead (interfaces, abstract classes, inheritance, extensions, overloads, dynamic dispatch, object expressions and others).

Problematic features

Those are the features that you most probably will have issues with, so I suggest avoiding:

  • Query expressions (docs). They are not well supported and have problems that are not fixed for a very long time (example).
  • Type providers (docs). Although they can be friendly to consume, writing them is a tedious and an unpleasant experience, which led the the fact that the ultimate majority of type provider libraries are abandoned and not supported properly.
  • Quotations (docs). While potentially very powerful feature for meta-programming, they are not widespread (except one unit-testing library), because of number of limitations and bad performance. I wish they would become more usable one day.

Double-edged sword

This is the a list of features that can be useful is some occasions, but you better not use them extensively:

  • Statically resolved type parameters (docs). This is a poor-man’s replacement for the structural type system and while can be very useful in some cases (example), can quickly complicate your code and worsen compilation time and binary size without good reason. You shouldn’t build core business abstractions or architecture with them. There is a relatively popular library FSharpPlus based on SRTP, which brings a lot of “functional” flavor to F#, but debugging and comprehending code that uses this library is reportedly being very complicated.
  • Tuples (docs). From readability and refactorability perspective you should prefer records or anonymous records to tuples. Still many existing F# APIs leverage tuples, so you’ll have to use them anyway.
  • Asyncs (docs). Somewhat ironically you shouldn’t use async expressions for asynchronous operations in most cases, Task is a simpler and more performant option. You might want to use Async in three main cases — when you want to implicitly pass CancellationToken to be checked for on each CE line, or when you want to do custom scheduling of your work (like running it in parallel or in specified chunks), or when want to use recursive asynchronous function (since tasks don’t support that well enough and will fail with SO or OOM on deep recursion).
  • First-class functions (docs). This might be the most unexpected point, what I mean here is that while it’s perfectly fine to use them for small utilities (like Array.filter etc) it’s tempting to use them for dependency injection, which will hurt eventually. I’d recommend you use a better way for DI instead. Also function composition (>>) appears to be much slower than piping.
  • Partial application (docs). Same as above, it’s fine to use it in limited scope, but not for dependency injection.
  • Lists (docs). Lists are mostly useful for recursive algorithms. However simple iteration, filtering and other general operations are very ineffective on lists, you should default to Array instead.
  • Custom operators (docs). It’s very easy to define custom operators in F# and while it can be indeed useful (example) you should avoid defining more than 1–2 of them, since for newcomers to your project it will be overwhelming.

Bread and butter

These features are perfectly fine to use as freely as you want and their combination defines F# look and feel — what it’s developers like the most:

  • Type inference (article). This is presumably the most important feature of the language that makes the code look concise and readable. There are certain cases where you have to specify types, but mostly you only need to specify them in the public API or signature files.
  • Expressions everywhere (docs). This feature makes it easy to compose your code easily.
  • Immutability by default (docs). It is very important feature that protects you from accidental bugs.
  • Generics (docs). Implicit and explicit generics fit nicely in you code together with auto generalization and flexible types.
  • Modules (docs). Basic block for code organization. However, modules are not first-class citizen in F# (like in OCaml)
  • Records (docs). Basic building block for data structures (together with anonymous records).
  • Discriminated unions (docs). Another basic block for the data structures with variants.
  • Lambdas (docs). Traditionally regarded as an FP feature. Improved with shorthand syntax recently.
  • Pattern matching (docs). Powerful tool with the main ingredient — exhaustiveness check. Active Patterns are also useful for repetitive patterns.
  • Computation expressions (docs). Apparently, the most outstanding feature of F# which provides a convenient DSL for the large amount of cases (not just monads).
  • Pipes (|>). This is the most important and well-known operator in F#. It allows creating beautiful data flows.
  • Units of measure (docs). They make your primitives type-safe with zero overhead. Additionally powered by UMX.

Conclusion

I’ve counted 12 smart functional features in F# , 7 half-smart and 3 not that smart. Is it enough to call it “functional first”? At the same time there are loads of essential OOP-related features that F# provides. My understanding is that F# should be regarded as a “hybrid” language nowadays, closer to Kotlin and “modern” C# than to functional languages like Haskell and Scala, but other opinions are possible as well.

Thanks for reading! Consider commenting here or reaching me out in social networks if you’d like a similar article about the OOP related features in F#.

--

--