
The Early History of F# [pdf] - strangecasts
https://dl.acm.org/doi/pdf/10.1145/3386325
======
tejasv
We, at Chaldal (YC S15, chaldal.tech), use F# in production. Most of our
backend projects are in F#, the last one is being migrated over slowly from
C#. Our new frontend projects are also in F# (using Fable).

I want to share some insights of using F# with the community. We started from
a C# codebase, and realized that a better language can help weed out most bugs
in our system. And yes, it works. Pretty much all bugs we face these days are
parts where the F# world touches something non-F#, like .NET and other
libraries written for C#, where interaction (like nulls) is not well-defined.
We've taken Scott Wlaschin's (fsharpforfunandprofit.com) teachings to heart,
and we have a giant banner in our office that reads "Make illegal states
unrepresentable".

It took a bit of learning for everyone to jump in, but our dev team has loved
the experience as the language is a pleasure to use; when they need to go back
to write C# or TypeScript code, a lot of these learnings transfer. People just
become better programmers (as is true with learning any functional language).

To get all the benefits of F#, you must adopt the whole paradigm. While F#
allows C#-like OOP, and while this can be an initial stepping stone in the
path towards F#, you must go all the way. If you simply do OOP, the trade-offs
aren't worth it, IMO, as F# is a functional-first language, and the OOP is
mostly provided for interop with the rest of .NET.

IDE support has been janky in the past, but its improving. Latest VS 2019 is
pretty good, and JetBrains Rider works pretty well on the Mac.

~~~
throwaway_pdp09
I get the "Make illegal states unrepresentable" in theory but how do you do it
actually for nontrivial preconditions like "this list must be sorted"? (as a
precond for a binary search on a vector, for example).

OOP is fine with functional if you make it immutable too, so I don't see the
problem (I've not really got a problem with mutable OOP, or state generally,
if it's done carefully).

~~~
choeger
> how do you do it actually for nontrivial preconditions like "this list must
> be sorted"?

This depends on your data model, of course. But for a list of integers you
could do the following:

1\. Have a unique data type that is the list of Deltas, plus the initial
element (So for instance the list [5, 3, 6] would be encoded as (3, [2, 1]).

2\. Provide a sort function that creates such a sorted list.

3\. Make the sorted list your input parameter.

This is obviously oversimplified, but I hope you get the idea.

A cheaper alternative is to

1\. Make an opaque datatype "sorted list", together with a function that
translates this type to a normal list. Implemen this type just as a list, but
keep the implementation private.

2\. Provide a function sort, that is the only function that can yield that
type.

3\. Demand that type as input.

------
chrisaycock
_Type providers_ were the biggest innovation I learned from F#. Type providers
are a way to have static typing determined from an external source, like a CSV
file or SQL database.

I ended-up using a similar trick (with different syntax) in my own language,
Empirical. I needed a way to infer a Dataframe from a file while in a REPL. It
wasn't until I read the MSR paper that I realized I could do it entirely in
the compiler without creating separate logic for the interactive users.

[https://dl.acm.org/doi/10.1145/2908080.2908115](https://dl.acm.org/doi/10.1145/2908080.2908115)

~~~
ridiculous_fish
Will you please share your insight? What are Type Providers? What is their
advantage over, say, naively dumping type definitions from pre-processing a
CSV file?

~~~
chrisaycock
The user could certainly dump a type definition, but for users who just want
to point to some data in one go, the F# type provider[0] works with just:

    
    
        CsvProvider<"trades.csv">.Load("trades.csv")
    

I personally didn't like having to list the file twice, so in Empirical[1]
it's just:

    
    
        load$("trades.csv")
    

My current version requires the dollar sign to tell the compiler that the
parameter will be _static_ (known at compile time). I'm planning on an updated
version[2] that will eliminate the dollar from the function call:

    
    
        load("trades.csv")
    

Basically, it means that users can just load the file like in a dynamically
typed language, except that Empirical is statically typed.

[0]
[https://fsharp.github.io/FSharp.Data/library/CsvProvider.htm...](https://fsharp.github.io/FSharp.Data/library/CsvProvider.html)

[1] [https://www.empirical-soft.com](https://www.empirical-soft.com)

[2] [https://github.com/empirical-soft/empirical-
lang/issues/23](https://github.com/empirical-soft/empirical-lang/issues/23)

------
strangecasts
Published as part of the History of Programming Languages (HOPL) IV
proceedings[1], which also include articles about C++, D, Groovy, and
JavaScript, and a few other languages.

[1]
[https://dl.acm.org/toc/pacmpl/2020/4/HOPL](https://dl.acm.org/toc/pacmpl/2020/4/HOPL)

------
bern4444
I've recently become interested in F# after I saw its use in a bunch of videos
by Scott Walashin. I came across his talks, and subsequently his blog, as I'm
getting more into functional programming and he has some awesome content.

I use Javascript/Typescript professionally and I really like both of them. F#
though has a minimalism in its design that is very appealing. Code I write in
F# feels a lot less cluttered as opposed to the same function in
Javascript/Typescript. This minimalism seems to be part of the language design
with some following specific examples.

\- `let` is used to declare variables and functions.

\- The last statement in a function is implicitly the return value.

\- The type safety/mechanism is great and seems to be extremely similar to
Typescript's so its easy to pick up for me (I'm not sure which came first or
if there's a relationship but it certainly wouldn't surprise me).

This along with some classic functional ideas that are built in like
immutability by default, automatic currying of functions etc all allow for a
language that seems be made to get out of the way. That's a very compelling
idea for a language design to me that I previously haven't seen.

Because functions and variables are both declared with let, perceiving
functions as data kind of finally clicked for me since I first heard that
concept 3 or so years ago. Another feature was to not require a semicolon as a
line terminator and instead use it for another meaning as a separator. While
JS doesn't require semicolons at the end of a line, its reserved for that
purpose.

Overall, I find F# to be a refreshing language (from javascript, python, java
and even others like Rust or Go but I haven't used those as much so don't want
to make bigger claims) and I'm glad to see a post like this on HN almost like
validation of a community behind the language. I'd be curious to get a feel
for some companies that actively use it as well.

I also love that there's a compiler for F# to Javascript so I can use it to
build front end code as I learn the language. Its pretty incredible we can do
cross compilation of high level languages like this and with others like Rust
and Go to wasm etc

Edit: formatting of bullet points

~~~
logicprog
Wow this is a great comment about a new-to-FP viewpoint on F#. I came to it
after I'd already experimented with Haskell and OCaml, used Rust a lot, and
been programming with Lisps for years - and I was really impressed with it
nevertheless!

I really love the ML-family type systems. They're really great aides to
programming, and makes planning really nice. F# has that, plus that usability
from C# and TS. It's also, like you said, got a really beautiful syntax IMHO.

I keep coming back to Rust though, because Rust is more general purpose, more
performant, and applies better to the stuff I like to work on for my hobby
projects. I'd love to use F# more but can't find a reason to at the moment.

TypeScript, Python and Rust have become my comfort languages, but I like Rust
the best, because it's got a lot of really nice features from ML languages and
a powerful and safe type system.

~~~
kowalgta
We use F# to build fairly complex portfolio management apps. It's a great
match for F# to Javascript transpilers (Fable, WebSharper). It makes it easy
to share functions and types between UI and backend and to have powerful type
safety. I.e. you change your DB data type and compiler informs you where in
the UI you need to make appropriate changes.

This works great with "makes illegal states unrepresentable" approach. It
helps to reduce a need for boring unit tests and lets you focus more on
expressing domain in code directly.

~~~
bern4444
I love this idea too. Is it possible to create a type (in F# or Typescript) to
represent an idea like greaterThan2? A value whose type is greaterThan2 would
have the obvious constraint the value is always bigger than 2. Having the
compiler check such a condition would be awesome.

I kind of can do it for Strings by doing something like this

    
    
      type ValidStrings = 'name' | age | 'dob';
    
    

The easy way around this is a function to determine this

    
    
      function greaterThan2(num) return num > 2;
    

but it'd be cool to express this level of dynamism as a type.

~~~
kowalgta
As others mentioned - using smart constructor technique, but not directly as
F# has no dependend type capability.

Smart constructor technique works well with 'parse, don't validate' approach
[0]. You can push type construction to the boundries of your system so that
you can work on a domain code with more precise types. It's not always so rosy
however as too much types can become a burden.

[0] [https://lexi-lambda.github.io/blog/2019/11/05/parse-don-t-
va...](https://lexi-lambda.github.io/blog/2019/11/05/parse-don-t-validate/)

------
xvilka
The original OCaml is more interesting. It has language flexibility F# lacks,
gets new features every year, crossplatform, and compiles into the native
code, thus faster. Once Multicore OCaml [1] project is finished the major
painpoint of it will go away.

[1] [https://discuss.ocaml.org/t/multicore-ocaml-
may-2020-update/...](https://discuss.ocaml.org/t/multicore-ocaml-
may-2020-update/5898)

~~~
jolux
I love OCaml. F#, if you squint, is kind of philosophically like Clojure for
the CLR: it leverages a heavily object-oriented but nonetheless very powerful
runtime to allow developers to create strongly-typed functional programs that
interoperate with a massive array of first- and third-party libraries that
already exist.

I wouldn't go so far as to say OCaml is more interesting. F# has things like
computation expressions, async, units of measure, and type providers, none of
which are trivial (or exist in OCaml), several of which are incredibly
influential, and all of which are interesting. The long slog towards multicore
for the OCaml community has been kind of embarrassing, with Haskell having
offered arguably the best concurrent and parallel programming support in all
of software development for years and years now.

I will also note that F# gets new features fairly frequently as well, is
cross-platform, and is _extremely_ fast by virtue of running on the CLR, which
I would not underestimate: [https://benchmarksgame-
team.pages.debian.net/benchmarksgame/...](https://benchmarksgame-
team.pages.debian.net/benchmarksgame/fastest/fsharp.html)

The features that it is missing from OCaml are admittedly pretty major,
though. Row polymorphic records and the structurally typed object system are
missed, not to mention rich modules and functors.

~~~
yawaramin
OCaml has the equivalent of computation expressions since 2018:
[http://jobjo.github.io/2019/04/24/ocaml-has-some-new-
shiny-s...](http://jobjo.github.io/2019/04/24/ocaml-has-some-new-shiny-
syntax.html)

It definitely has async:
[https://github.com/ocsigen/lwt](https://github.com/ocsigen/lwt)

It doesn't have units of measure but due to strong abstraction properties of
modules we can fairly easily roll abstract types.

It doesn't have type providers but it does have plugins for type-directed
derivation of JSON codecs, equality, comparison, printing, etc.

> The long slog towards multicore for the OCaml community has been kind of
> embarrassing

IMHO it's been refreshing to see the pushback against 'multicore at all
costs'. There is a benefit to taking the time to do things the right way.
OCaml is a 30+ year-old language with an even longer ML heritage. It's going
to be around for a while; it doesn't need to rush into a sub-par
implementation. Meanwhile, people are writing highly concurrent, multi-
threaded applications with it right now, and using multiple processes just
fine for parallel computing.

~~~
jolux
I’m glad to see that the language is moving forward in these areas, but I
don’t think you understand how F# implements units of measure. It’s not just
another data type, they’re fully inferred and checked and work with generics:
[https://stackoverflow.com/questions/21538563/can-f-units-
of-...](https://stackoverflow.com/questions/21538563/can-f-units-of-measure-
be-implemented-in-ocaml)

Type providers are a native feature, no plugins required. I have no animosity
towards OCaml (in syntax and semantics, I prefer it!) but this is a bit of a
stretch.

I don’t think anyone would say Haskell does multicore the “wrong” way, and
OCaml as you say has had decades to solve these problems. I love the language
but the community is very small and the language evolution has been extremely
conservative for the most part.

~~~
yawaramin
> I don’t think you understand how F# implements units of measure. It’s not
> just another data type, they’re fully inferred and checked and work with
> generics

Yes, units of measure are really cool, and I understand they support
dimensional analysis out of the box. It's quite unique, and I fully agree that
abstract types don't _fully_ replace them. Just saying that that's what people
do in OCaml.

> I don’t think anyone would say Haskell does multicore the “wrong” way

And it certainly is not something that I said.

> OCaml as you say has had decades to solve these problems.

The 'problems' being the lack of multicore support? As I said, this is not
really stopping people from getting real work done in industry–see everyone
using OCaml currently in highly concurrent and multi-process parallelized
applications, or in fact even people using NodeJS, Python, Ruby.

> the community is very small

Correct, but it is definitely growing.

> and the language evolution has been extremely conservative for the most
> part.

Not so correct. There have been massive amounts of changes in the last few
years: [https://www.ocamlpro.com/2019/09/20/a-look-back-on-
ocaml/](https://www.ocamlpro.com/2019/09/20/a-look-back-on-ocaml/)

And this is not even taking into account the features which OCaml had before
then beyond F#:

\- Polymorphic variant types \- Structural subtyped object types \- Named
arguments \- Optional arguments \- Functors

~~~
jolux
F# has named and optional arguments and I named everything else as real
differences in my initial comment in this thread.

I’ve interviewed with Jane Street and gotten an offer on the basis of my OCaml
and F# skills. I understand where these ecosystems differ. I rest my case.

~~~
yawaramin
From [https://docs.microsoft.com/en-us/dotnet/fsharp/language-
refe...](https://docs.microsoft.com/en-us/dotnet/fsharp/language-
reference/parameters-and-arguments#named-arguments)

> Named arguments are allowed only for methods, not for let-bound functions,
> function values, or lambda expressions.

> ...

> Optional parameters are permitted only on members, not on functions created
> by using let bindings.

~~~
jolux
I concede that point.

------
algorithmsRcool
F# is the language that I keep coming back to. And as the paper notes, it has
kept up well with the rapidly changing landscape of .NET Core and newer
defacto standards introduced by modern C#.

~~~
GiorgioG
It had a very rocky start in the .NET Core world thanks to Microsoft's lack of
interest in supporting F#. It lagged behind C# in significant ways and
continues to get very few resources behind it.

~~~
jackfoxy
Actually Microsoft has quite a respectable team of developers dedicated to F#.
It is true development suffered because the Roslyn compiler that MS invested a
lot of effort into cannot be made to work for the language. F# has been made
compatible with surrounding Roslyn tooling over time. (There is a distinction
between the Roslyn compiler and the Roslyn tooling I am not qualified to
explain.) The fact is that as a functional first language it does not require
as much surrounding tooling as C#. Requirements for refactoring tooling, for
instance, are far simpler.

~~~
GiorgioG
> Requirements for refactoring tooling, for instance, are far simpler.

If that's the case, it's sad that Visual Studio's refactorings for F# are so
meager.

~~~
JanneVee
Yet I don't miss any refactoring tools in VS for F#. It is a simpler language
and has some nice design affordances that makes refactoring superfluous in
some cases.

I have a small story around it. A couple of years back I had the nasty habit
of enumerating sequences multiple times with LINQ. Resharper constantly
complained about it. Programmed some F# for projecteuler and advent of code.
My nasty habit of multiple enumeration went away even in C#, because F# design
affordances made me rethink enumeration and sequences. So since then I haven't
triggered multiple enumration in Resharper with my own code. It is always
somebody elses.

------
lihaoyi
I have a special place in my heart for F#. While I do a lot of Scala these
days, professionally and open source, F# was my first introduction to
functional programming that eventually got me into Scala.

F# and Scala are extremely similar languages: garbage collected, hybrid OO/FP
guest languages hosted on a widely-used runtime. Even though the superficial
syntax is very different, and the advanced language features they provide are
pretty different, the core experience of modelling your data as "dumb" data
structures and transforming collections of data using higher-order functions
is almost identical.

Scala ended up winning out for me due to a broader ecosystem (Much more OSS
Java than OSS C# out there), better tooling (Visual Studio's F# support was
always disappointing...), and easier interop (F# <-> C# feels a lot more
clunky than Scala <-> Java). But I can easily imagine an alternate universe
where I'm happily writing F# all day, and almost nothing would be different
from my current work writing Scala.

------
rurban
Oh my so many warts. In haskell, ocaml but also F#.

> Specifically, method constraints were added, introduced by a deliberately
> baroque syntax:
    
    
      let inline (+) (x: ^T) (y: ^U) : ^V = ((^T or ^U):
       (static member op_Addition : ^T * ^U -> ^V) (x, y))
    

> This definition says that any use of + is implemented via inlining a call to
> an appropriately-typed op_Addition method, defined on the type ˆT or ^U,
> i.e. the type of either the left-hand or right-hand argument. The ˆT
> notation for type variables indicates statically resolved type parameters
> (SRTP), i.e. type parameters which are resolved to a nominal type at
> compile-time.

Why ^ and not using the existing : for seperating types from values? :T * :U
-> :V would have looked like types, not pascal ptrs.

Equally interesting are ocaml warts like let x2 = 1.0 +. 2.0 (+ not overloaded
or defined for floats), or haskell's decision to torpedo the "class" keyword.

Or ocaml hijacking ::

let xs = 1 :: xs, with :: being cons, which was initially the lisp dot
notation '(1 . xs)

In retrospect perl's syntax is sane compared to this.

~~~
smabie
I agree, though Haskell is certainly the most advanced. ML languages tend to
be... primitive, at least to the modern eye. The lack of function polymorphism
in OCaml is such a huge bummer. Modular implicits have been proposed (in a
paper from 2014), but it doesn't seem that they are being actively worked on.
If I could get multicore and modular implicits, OCaml would be _the_ ideal
language.

~~~
octachron
Modular implicits are being worked on actively, it is just a hard feature to
get right in a scalable and future-compatible way.

------
addictedcs
There are a couple of concepts in F# that I really like:

* actor-based approach to concurrency[1]. Very useful when you want to design your code lock-free. Treating instances as agents that read messages from a mailbox, frees the developer from using low-level threading primitives. First time I've used this approach in production with AKKA and Scala. In F# it is much cleaner because the language itself is built on a better platform.

* exhaustive pattern matching[2]. Writing in F# means using pattern matching a lot. Exhaustive matching gives you the confidence to refactor and maintain your code. The compiler will warn you when adding a new value in your type and not covering the execution path that uses it. It catches a lot of errors before you even push the changes to CI.

* obviously immutability, conciseness, currying, these have been mentioned so far by others.

One nitpick in F# are Exceptions [3]. They approached it similarly to how it
is done in C#. You are allowed to define and throw an exception that will
"jump" somewhere in the execution path. I prefer when a method returns Option-
style types. This way, you know what to expect from a call, and pattern
matches on the result without adding one more execution path in your code,
which covers exceptions separately. It was added to support C# style error
handling, though I very much prefer an error-code-based approach.

[1] [https://fsharpforfunandprofit.com/posts/concurrency-actor-
mo...](https://fsharpforfunandprofit.com/posts/concurrency-actor-model)

[2] [https://fsharpforfunandprofit.com/posts/correctness-
exhausti...](https://fsharpforfunandprofit.com/posts/correctness-exhaustive-
pattern-matching)

[3]
[https://fsharpforfunandprofit.com/posts/exceptions/](https://fsharpforfunandprofit.com/posts/exceptions/)

------
dang
A draft was discussed last year:
[https://news.ycombinator.com/item?id=18874796](https://news.ycombinator.com/item?id=18874796)

