Hacker News new | past | comments | ask | show | jobs | submit login
A Brief F# Exploration (srid.ca)
109 points by todsacerdoti on April 9, 2021 | hide | past | favorite | 60 comments

I agree with the author about the slow inner loop experience. Recompiling the whole app to see browser changes gets tiring. The .NET team is planning a series of updates to address this in .NET 6. https://github.com/dotnet/core/issues/5510

I also dislike the default workflow for dealing with Nuget forks. I switched to Paket for dependency management which makes this much simpler. https://fsprojects.github.io/Paket/

> The .NET team is planning a series of updates to address this in .NET 6.

This is already bearing fruit in the latest preview: https://devblogs.microsoft.com/dotnet/announcing-net-6-previ...

I also saw this lovely update for Xamarin.Forms users https://twitter.com/drasticactionSA/status/13802626828724428...

This is a very strange criticism given that F# was the first language to have a REPL and nowadays most web projects also have a compilation step.

Do you mean The first dotnet language?

Yes of course, I forgot that word.

Actually no, because on the release CD of Common Language Runtime, and the alphas and betas that preceded it, Microsoft collaborated with several companies and universities to have various languages available at lunch date.

Many of which did have a REPL.

Also the dynamic language runtime originally designed for Iron languages, Python and Ruby, precedes F#.

My favourite thing about F#/.NET Core (I guess .NET 5 now) on Linux is how easy it is to install multiple versions side by side using my distro package manager. This spoiled me so much now I'm disappointed when I have to use conda/rustup/etc.

Consider using nix package manager. You can have multiple versions for every tool/library you like.

Nix looks great, but you have to go all in on Nix. Sometimes it's simpler to install a few tools locally but not control the entire environment.

You can install nix side by side to your distro package manager and only install some packages using nix. You could also use something like lorri and direnv and let nix only manage your code projects and when entering the project directory, drop you into a shell with only the dependencies you need for this specific project installed.

Your usecase is what nix-shell is for. It loads an environment on top of yours for just a single project.

guix is similar but can be used on OSes with a traditional package manager. I personally prefer it.

F# is a great language. I just wish it compiled as fast as OCaml or even anywhere near as fast. I also wish it produced a single static binary, though I guess this is now possible in .NET? It’s been a while since I toyed with it.

You can produce a single file binary, with the caveat it includes the entire .NET runtime, which leads to massive build artifacts. Though it is better than no support at all I suppose

That is no different than in languages like D or Go, the runtime comes along.

OCaml would meet all the requirements. Any thoughts on it?

OCaml type system is more powerful than F#. OCaml lacks operator overload, computation expressions (unless you use an extension?) and threading (coming soon?). The community is tiny for OCaml. F# is small too but it can access all of .Net easily and there are some good IDE options. F# is backed by Microsoft.

F# is low risk to adopt compared to OCaml. F# is the functional language pick that won’t get you fired.

OCaml is maintened by the INRIA and is widely used in french academics.

In the long term there is far less chance that the INRIA drop OCaml than microsoft drop F#.

And if you already are a .net shop, adding some F# code in a dependence is really smooth.

That is until Microsoft release a version of the Framework or a version of VS that breaks F# in your project. I dropped F# support from my one of open-source projects because of this, and at my company we've decided to stop all future F# development. The move to .NET Core was an absolute clusterfuck for F# ... well, it was for C# too, but everything broke in the F# world ... I even ended up on a Skype call with the PM of the F# team as he tried to help resolve my issues; it's amazing he was willing to help, but it was kind of indicative of a bigger issue (I believe).

The tooling is poor in general, the interop is questionable at best. One example is `Option a` in F# becomes `FSharpOption<A>` in C#, with `None` being `null` - breaking all of the type-safety of the union-type. It isn't seamless in the way some might have you believe. The standard answer is build a wrapper to expose F# to C#, which is ok, but is an extra level of indirection and complexity that shouldn't be needed (if indeed the idea is to have seamless interop).

I developed language-ext [1] to bridge the divide, but in the end there isn't a strong enough reason to write F# all the time in the .NET sphere. It is compromised by an ecosystem where C# is dominant. That includes the framework libraries and even the limitations of the type-system in F# (no higher kinds for example). There's no real reason for F#, other than a nicer syntax, it doesn't have a killer USP like Haskell (purity) or Erlang (concurrency).

If it wasn't in C#'s shadow, and it was able to run free, and without compromise, there could be a compelling narrative - but what we're doing now is moving to PureScript for our front-end, and Haskell for microservices where we need the USP of Haskell (purity and strict domain modelling).

[1] https://github.com/louthy/language-ext/

There is barely any reason for FSharp to exist if not for dotnet interop(from the fsharp history paper). There are some novelties but they came later.

Any language developed on dotnet platform will always be in C#'s shadow. F# is much nicer to write than C#(for now), if you need to develop on dotnet.

I want a statically typed functional language with a big ecosystem. I want to use the same language across front-end and back-end.

What are my options?

- PureScript on web and Node.js

Can use NPM packages! Node.js as a run-time lacks features like shared memory across threads. PureScript is still too early IMO.

- Scala on JVM and web using Scala.js

Not a bad option, although the language is very complex. More towards the OOP end than the ML end in the spectrum of language design. JVM can be a good or a bad thing, depending on what you are doing.

- F# on .NET Core and web using Fable

Simple functional language with access to the .NET ecosystem. ASP.NET has great performance. Access to NPM packages on web. Lacks some advanced type-system features in Haskell, OCaml and Scala. However, the community has a pragmatic focus as a result.

- OCaml and ReasonML

ReasonML has limited backing from Facebook. OCaml has support from a few financial institutions (e.g. Jane Street) but it's not on the same level as what a big tech company could provide. Tiny (but high quality) community and ecosystem. OCaml has great single-threaded performance but mult-threading is not quite ready. Already small community split by compiler extensions and standard libraries.

- Haskell and GHCJS

Again, tiny community and ecosystem. Beautiful language, but lazy evaluation has bit me too many times. Steep learning curve since newcomers cannot fall-back to OOP and imperative code.

- Elm

Tiny ecosystem, tied to Node.js (see PureScript). Interop story with NPM modules is worse than PureScript.

For me, F# is the clear winner.

If you aren't particularly interested in functional programming - maybe you just use some delegates here and there - then C# is a fine choice. Otherwise, C# quickly becomes awkward compared to F#. Besides, C# to JS options are not as mature as Fable.

Also Clojure/ClojureScript if you're willing to give up the static typing.

Clojurescript with Re-Frame / RE-Frame-10x / Reagent has transformed front-end for me. It's so good.

Re-frame (just like Clojure in general) seems to be much more stable than alternatives in a sense how much things change over time.

Clojure is such a different school of thought! Most of the arguments I have heard for Clojure (immutability, thinking in data, etc) also apply to ML though. Still, I hope to try it out properly one day!

I wonder what is easier for a strong OOP developer to learn, Lisp or ML?

Lisp and Clojure have CLOS, which is more powerful than what most people know as OOP.

ML, depending on the flavour gets pretty how one combines functors, aka generic modules, which is similar to component based programming like COM, or just plain objects as in OCaml.

Now the immutability isn't as hard to adapt to as one may think.

Clojure has CLOS? That would be new.

Fair enough, I was over simplifying, still it has a nice subset of it.

What is this 'subset'? CLOS is based around class&inhertance&mutability + generic function dispatch over classes. Spiritually Clojure wants to avoid OOP, where CLOS is a full object system with an optional meta-layer.

I know you are a Lisp expert with a deep knowledge of its eco-system, but trying to downplay the existence of multiple dispatch and protocols doesn't help.

Also I bet that The Art of Metaobject Protocol reference implementation can be ported into Clojure without major issues.

The Art of the Metaobject Protocol has no 'protocol reference implementation'. Neither CLOS nor AMOP supports explicit protocols. Explicit protocols are a feature of Clojure.


"A protocol is a named set of named methods and their signatures"

Such protocols are not a language feature of CLOS.

AMOP describes a 'Metaobject Protocol' (MOP) and has a tiny example implementation (called Closette). This is entirely different from what the 'protocols' feature does in Clojure. A 'protocol' in AMOP is informal.

What AMOP actually describes with its MOP, is a meta-level of meta-classes and generic functions, which implement large parts of CLOS itself. Such the classes, generic functions, methods, slot descriptors, etc. are CLOS objects themselves.

Clojure supports a form of multiple dispatch, but the dispatch mechanism is also very different from CLOS - for example it uses a dispatch method per generic function, selects one method and has no idea of a next method. CLOS for example uses a built-in dispatch mechanism, multiple class-based dispatch hierarchies for multiple arguments, using a set of methods which are selected based on the class hierarchy.


Typically it is not a good idea if Clojure uses a name for a feature, to generally think that this is similar to a feature with a similar name in Common Lisp -> 'protocols' and 'meta-object protocol' are two very different things.

Porting the CLOS MOP to Clojure would be possible, when one first implements CLOS in Clojure. Otherwise it makes very little sense, since Clojure does not by default support the (older) view of object-oriented programming of CLOS. Something which the Clojure designer thought of a feature: getting rid of things like mutable objects, class based inheritance, class-based OO, etc.

The interop story really needs to be fixed quickly. There will always be some rough edges but things like basic collections (Sequences, Lists, Arrays, ImmutableArray, Option...) and high performance things (Tuples, Tasks, including ValueTuple and ValueTask) must just be fluent across the languages even if it means backwards compatibility is broken or the types work less than ideally on both sides of the fence.

I mean it works for my cases. We interop mostly small libraries where we need/prefer a better functional shell. I guess those rough edges start kicking on larger code base.

The move to core was messy, but I am personally glad they did it just for the better linux support.

Great library by the way thanks for making it!

The move to .NET Core broke everything. PowerShell is still broken, with fundamental issues that may remain unresolved for a long time. I don't expect most people to switch over until PS v9.0 at the earliest.

What part of Powershell is broken? Are you referring to module compatibility or other issues?

For my shop, the AD module was an obstacle until v7. Many Azure-related modules still seem to be 5.1 only.

The module compatibility thing isn't just developers being lazy and not porting over their modules, it's actually a fundamental thing to do with the way .NET Core was architected.

Because -- you know -- in the 2020s there are only web apps and mobile apps. There are no other types of "applications". That unnecessarily long word only exists in dead tree dictionaries like Oxford, and is used by old people that still cling to their "personal computers" of yesteryear. So of course, .NET Core was made only for web apps, with a hint that mobile apps might one day work also. Microsoft will say otherwise, of course, but in practice 100% of the decisions were to ship an MVP that can be used to create a web application. All the doco was for web apps. All of the samples were for web apps. Whenever there was a decision to be made, they made it with web apps in mind.

This has made some things impossible, especially related to actually loading dynamic link libraries (DLLs) dynamically, which despite the "dynamic" in the name is surprisingly problematic in .NET Core.

It really wants you to ship a monolithic, statically linked app. Anything else is just a nightmare of compatibility shims and flags that ought to be at the DLL level but are actually set (once only) at the EXE-level.

This makes PowerShell Core modules very sensitive to the exact version of .NET Core used to compile PowerShell itself, contrary to how the old PowerShell used to work, where you could upgrade the system framework to enable the loading of a newer module built against that framework.

Similarly, if there is another module that pulls in something with compatibility shims (or not), then that can break your module in weird and wonderful ways.

I tried to write a .NET Core module for PowerShell core several times, and every time I got bogged down in reams of inscrutable internal framework errors that had 2-3 hits maximum in Google, all from other people complaining about PowerShell development having being broken.

Not if you are doing UWP, where F# support is an hack and it won't be ever supported officially (there is a github issue about it).

However given the whole Project Reunion and uncertainity how both worlds are supposed to be merged, maybe it isn't such a big deal.

Ah and don't expect Code Generators to ever support F#.

I stop doing UWP when WindowsPhone dies. I have no clue how it is going there either in F# or C#. It was a nice platform. I hope it still is.

Well, it is still the future, because UWP is also COM vNext, I know Microsoft marketing sucks.


> Ah and don't expect Code Generators to ever support F#.

What sort of Code Generators are we talking?

I ask because I have a very early alpha project that does F# code generation using Fantomas. Apart from an (uninvestigated) slightly higher startup time than I hoped for, it seems fine.

The ones in .NET 5 and .NET 6, integrated into Roslyn and a key part of the upcoming AOT support.


The main goal is for them to be the .NET version of Java's annotation processors and thus allow many libraries to replace their reflection code with compile time code generation.

Oh wow, thanks for that. I’m new to .NET and didn’t know anything about Source Generators. That seems to be a pretty interesting feature.

I followed the link to the F# suggestions and found


It seems like there is interest but with a "let’s wait and see what happens with C#" kind of approach. I’ll admit it looks far off, but there appears to be interest.

I’m certainly not expecting them, and for my purposes F# provides more than enough anyway. I am nonetheless interested: why do you think they will never come to F#?

Because Roslyn compiiler stack is only for C# and VB, F# has its own infrastructure.

Until now, all the F# related tooling tends to be done by a mix of the tiny MS team and FOSS community, it is not the full steam rolling engine that C# and VB get.

>OCaml type system is more powerful than F#

In some ways, on the other hand F# has active patterns and type providers, which are quite magical.

The most impressive feature of F# clearly is its tight integration to dotnet/C#.

It’s super easy to mix C# and F# code in the same project and it works out of the box.

This allows you, if you want it, to write your domain code in F# and your technical code in C#.

Do you mean same solution? I didn't think you can mix languages over the same project in C# / F#

Yes, same solution, my bad.

There are a lot of places where interop sucks. Other than the ones already mentioned by someone else in a previous comment there are the events that need a lot of ceremony in F# to be compatible with C#, lack of implicit conversion that in some cases renders some C# libraries unusable unless you define some explicit conversion operator and probably some other stuff that now I don’t remember. If you can work only in F# it’s a much better experience compared to mixing C# and F#.

Only on regular .NET, the UWP story is a bit different.

I personnaly went from Ocaml to F# because it had much better tooling (IDE support) and a larger libraries ecosystem (access to all .Net libraries).

ocaml ecosystem is smaller than haskell. ocaml kind of meets the minimal requirement, but far away from production grade.

in frontend, bucklescript changes too quickly. js of ocaml is hard to use. good luck if you want to trim the code size.

OCaml has subpar windows support. The native version is experimental, otherwise it's cygwin or WSL.

> Must run natively on backend without nodeJS (rules out the likes of PureScript)

PureScript does have an Erlang backend as well.

My understanding is F# relies on a JIT just as Node (V8) does. Are either really native? Are both?

Yes, F# relies on a JIT, but not just as Node does. F# compiles to CIL, the .Net runtime bytecode format. It is much more like Python bytecode (pyc) or JVM bytecode (jar) than like Javascript.

FAFAIK Node doesn't compile to an intermediate language, it either compiles to minimzed (lets-just-remove-all-whitespace) Javascript, or it generates a native binary.

.class for java; .jar is just a zip file with some metadata.

F# can run in a few different places: .Net, node, browsers, .Net core, Mono and AOT compiled. Some targets have JIT, some do not.

It does, but you can produce self-contained applications that don't need the runtime to be installed (it's included in a native binary shipped with the application). Full AOT compilation is also being worked on.

I'm assuming that they primarily mean "I prefer not to depend on NodeJS for $REASONS". That's pretty fair, Node is amazing but there's definitely also plenty to dislike.

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact