It's very convenient and you've got a massive number of .NET APIs to fall back on.
The language is a little complex though. That you cannot call interface methods on an object implementing the interface without explicitly casting to the interface  is pretty weird. And getters/setters are a little complex.
If you want an easy introduction to the ML family for educational/historic sake I'd always recommend Standard ML. But if you want a highly pragmatic, mature, strictly typed, compiled, cross-platform language F# is pretty compelling.
In F# 6 things are changing a bit, and may possibly change in the future. There are various classes of cases where the type information is known, but the compiler still required you to explicitly cast things to be happy. That's no longer case. This link has 3 examples that don't compile with F# 5 but will compile with F# 6.
If the compiler already knows that it implements interface X Y and Z, it seems unnecessary to require it (unless those interfaces have conflicting names, in which case you would have to be explicit due to ambiguity).
I feel like many languages are walking away from being so explicit, in particular I've noticed C# becoming more implicit over the years.
Furthermore, it is also worth checking Dafny  (Spec#'s successor). The relationship between Dafny and C# is equivalent to the relationship between F* and F#. Both languages try to refine their predecessors semantics and augment them with practical constructs to prove program correctness.
Some of the largest software artifacts ever verified have been implemented in Dafny , and F* is also looking quite promising.
# . + are bad enough... but * ?!? It can't even be used in filenames at all.
And while Google might be ok at indexing it... pretty much every other fulltext search engine will be useless. Not to mention it can't be used in package names, domain names, filenames, and a heap of other things.
Even an emoji (while still terrible) would be better under some circumstances.
"Go" was ridiculous enough, ironically from somebody who works at Google. But these people still putting special characters in the names must really not care about findability/disambiguation at all.
PHP might have got a lot wrong... but the name is great... it's just "PHP" everywhere, even the filename extension.
That said, the F# code that you see in the compiler source looks syntactically more like OCAML and makes little use of .NET.
That's probably because in C#/.NET you can specify methods that are only valid when being used as this exact interface. Those are called explicit interface implementations and can contain completely different code than the class method.
Enter F#. I've been working on (yet not releasing a usable version yet bc I'm bad) this program entirely in F#, using Giraffe as the web layer, following the general architecture defined in Domain Modelling Made Functional, and using Dapper to write to a Sqlite database.
F# is amazing. I'm able to fully understand the codebase and follow exactly what happens from HTML Form request, through input validation, through the business logic, down to the database and back to the domain, then rendering out to a JS-free webpage via Giraffe.
It gets tedious doing the marshalling/unmarshalling from HTML form to input DTO to domain type to output DTO to database records to input DTO etc. and I need to learn both how to create DSLs (so users can type in rules from the book and the system will extract the keywords and compose them into the closest equivalent set of conditions and effects the system supports) and lock down my bounded contexts so I'm developing them in order of least to most entity dependencies rather than in order of where I feel I need them most, but I can't see a way where I would be able to build this in plain old C# without all of my above challenges + lots of boilerplate code to ensure my expected invariants are met and someone (me) hasn't changed an invariant out from under me somewhere else without realizing it.
I'd be interested to hear about other approaches. You could in theory just dump (automatically generated) JSON to the database and evolve your types with tagged versions, but then you have to write some boilerplatey converters between TypeV1, TypeV2, TypeV3, ...
I guess that's the price for staying away from full-blown ORMs?
It reminds me a bit of how in Elm you just suck it up and write the JSON en-/decoders by hand. Annoying at first, but then you get super obvious and maintainable code out of it, which is quite a joy.
The F# take on Elm coders (Thoth.Json) can use reflection to generate them automatically. This is really useful at the prototyping stage.
Make no mistake, it's a cool language that I'd love to be good at. The code always looks elegant, I'm just terrible at writing it.
Unfortunately there's some level of .NET tooling you need to understand before you can do much today, and several .NET concepts creep in with that.
The good news is that .NET Interactive is shaping up to be a great way to avoid most of that. At least so far all you need to do is install the .NET SDK, then install a VSCode plugin and you're good to go - just write code like you would in a python notebook, and acquiring packages is even easier than in Python: https://github.com/dotnet/interactive#notebooks-with-net
The hello world server:
let myHandler ctx =
ctx |> sendText "Abra Kadabra Kalamazoo!"
let myRoutes = [
get "/" myHandler
|> setRoutes myRoutes
F# currently seems easiest for c# dev to get started, but c# devs have almost no incentive to do so.
"Python needs to appeal to C# developers and other developers...
Python seems easiest for PHP devs to get started, but C# devs..."
And, the truth is, no, Python doesn't need to do that at all. It just has to be the best Python it can be.
Whereas, if you look at the examples in the Giraffe framework, you need to do a bunch of dotnet boilerplate. https://github.com/giraffe-fsharp/Giraffe#doing-it-manually
let webApp =
route "/ping" >=> text "pong"
route "/" >=> htmlFile "/pages/index.html" ]
type Startup() =
member __.ConfigureServices (services : IServiceCollection) =
// Register default Giraffe dependencies
services.AddGiraffe() |> ignore
member __.Configure (app : IApplicationBuilder)
(env : IHostEnvironment)
(loggerFactory : ILoggerFactory) =
// Add Giraffe to the ASP.NET Core pipeline
let main _ =
fun webHostBuilder ->
If you plan to use Python you have to learn some Python libraries.
I like how well written F# code is less verbose while being very readable.
I want to learn another language. I was thinking about Go, Rust and Kotlin. I excluded Kotlin because it seems it doesn't bring me much value over C# and I excluded Go for the same reason.
I dabbled a bit in Rust, but so far I don't like it's verbosity, the fact that it is boiler plate-ish.
So I reminded myself of F# and started learning that. I know that it has a huge disadvantage compared to the others, it is far less popular, but on the other hand, I can use F# in the same projects I use C# and some of the thing I learn while learning F# might be applicable to C#.
I feel that F# is a niche language mainly because of of two things: Microsoft doesn't care much about it to push it and help it to thrive. And they don't push it because it has a small community. I seems that Microsoft is investing more resources in Python or Java than in F#. The other thing is that the largest community sees it as a Microsoft language and that is enough of a reason not to touch it.
Whatever growing adoption F# has its due to efforts of it's tiny but very enthusiastic community which is helping it to jump some barriers like frontend programming.
Most of introductory material I found was good in explaining functional concepts, lisp approach to solving problems etc, but I thought assumed a working knowledge of the JVM ecosystem.
For someone utterly unfamiliar to Java/JVM world would find clojure extremely baffling, cryptic error messages, needing to be familiar with the rather large java standard library, etc.
It is a hard to swallow truth, but guest languages really require understanding of the underlying platform.
It can also be frustrating if you know a little OO (but not how Java does everything) and then try to learn a functional language that sits on top of one of these OO tar pits of an ecosystem. I can write Python classes, but seeing the complicated mess of Java is frustrating as that has to be learned as well. Nothing against Java btw (it has its place), but it does seem to require a lot of boilerplate.
I think that Java is best understood as an enterprise programming language, not as an object-oriented one.
There's a very peculiar class of programming languages that are a product of a specific period of history. At the time, companies were looking for ways to scale beyond the limits of the then-dominant procedural programming paradigm. But they had a lot of existing investment in it, and so teams were naturally hesitant to abandon it entirely.
This situation created an ecological niche that allowed a very peculiar class of programming language to take root. Java and C++ are the best-known examples, but there were others such as Objective-C. C# and Visual Basic.NET are probably the last notable examples. These were multi-paradigm languages that layered object-oriented features on top of procedural bones. Earlier examples such as C++ and Objective-C made this quite explicit, and were likely to even have their atomic types not be objects. Later examples eliminate some features such as non-object types and global functions and variables, but still retain much of their procedural heritage if you know what you're looking for. Probably the most notable one here is a tendency to favor explicitly state-oriented idioms over properly encapsulating state.
Java falls somewhere in the middle. It has later features like full-fledged garbage collection, but lacks others such as a consistently object-oriented type system.
I think that its tendency toward boilerplate directly reflects its stage in this evolution. Basically, what we perceive as boilerplate is really a hold-over from how procedural languages tended to be generally less declarative - it was up to the programmer to handle a lot more stuff for themselves.
In some very large OOP codebases adding features or fixing bugs becomes a nightmare. Every small change you make will break hundreds of tests and while you fix the code to pass those, you will break other tests. Thing that can take days at most in sane codebases might take weeks.
(That, or you should not have made the change that way in the first place. Much like best practices for API changes involve shipping a new version in parallel and then slowly migrating everything over to it, a breaking change to the behavior of a class that really does have a legitimate reason to be used pervasively - say, a collection - shouldn't be done by just suddenly breaking all the code that uses it.)
Which speaks to exactly the distinction I'm trying to draw when I say, "a tendency to favor explicitly state-oriented idioms over properly encapsulating state." Note that simply sticking getters and setters on your state is not the same thing as encapsulating it.
It's kind of hard to explain the difference except by example, so I'm not really prepared to dive into it here. To be honest, I think the best way to get a feel for it is to read and understand the source code of an early Smalltalk system. Smalltalk-80 is small enough that one can get a decent feel for how everything works in an evening or two.
Or that spot we were 100% certain was a bottleneck as it clearly wasn't micro-optimized C code, shows 0.1% hit on the V-Tune profiler.
While the clojure ecosystem has a lot of wonderful, bright and helpful people in it, and clojure itself is technically impressive (esp. its collections implementation), the new user experience isn't great.
I'm not an experienced emacs user, so that makes it doubly difficult, as the ironed-out workflows seem to assume that you are (which is fair, as emacs and lisp are like peanut butter and jelly). However, these days it's fair to assume that a new clojure user is probably learning 'clojure', 'lispy ways', 'java/jvm', and 'emacs' simultaneously.
I think that calva/vscode has a lot of potential to drop emacs off that list, which is welcome, as its learning curve alone is legendary. If you look at that list, it should not be surprising that the community is small. There's so much to learn that I it seems too much to even start.
I have found this video by James Trunk: https://youtu.be/C-kF25fWTO8?t=848 to have a very nice live coding example.
Borkdude is a total boss; babashka and clj-kondo are amazing.
Also, don't get me wrong, I reiterate that I already love clojure, I just wish there were fewer and lower barriers for others to join in.
I realize there are already people on that case like https://practical.li/, and are doing an excellent job. I also think that things like https://github.com/Olical/conjure are pretty awesome, being predominantly a vimmer.
I highly recommend 'The Little Schemer' if someone wanted to tread the same ground. Such a strange and fun little book.
Chapter 1: install and learn the JVM
Chapter 2: Emacs
Chapter 3, finally start learning Clojure if you made through the first two chapters. Those first two chapters are going to filter out quite a few potential Clojure learners.
It's a cliche saying, but for me F# just works! (tm)
I'm a huge fan of F#, but I had the advantage of knowing .NET first.
Also there are lots of learning resources here: https://fsharp.org/learn/ some for guys coming from scripting languages like Python. As a matter of fact, F# can be used for scripting, too.
#r "nuget: FSharp.Data"
And it pulls that third party package into your script context. Its kind of empowering when I can just copy and paste script text to my colleague (e.g. email/slack) and all they need to do is copy/paste the text into a single text file and it just works third party packages included with a vanilla .NET 5+ installation. Just run 'dotnet fsi scriptFile.fsx`. Auto-complete picks up all the new types as well.
Documentation seems pretty straightforward to me: https://docs.microsoft.com/en-us/dotnet/fsharp/tools/fsharp-...
I strongly believe that functional programming is not a good fit for 100% of software architecture. The best is some sort of hybrid. Generally, the closer you get to the business logic, the more functional you would want to be. The serializers, http servers, etc. are probably not worth the squeeze to force into a functional domain.
With C#, when I'm trying to use functional design, I often feel like I'm swimming against the current. The language has functional features, and it will certainly let you use them, but, LINQ aside, the path of least resistance is mostly imperative and object-oriented.
In F#, it's the other way around. It has a full suite of object-oriented features - still, after all these years, quite a bit more complete than C#'s suite of functional features - but the path of least resistance is mostly functional.
I don't want to say that's a universal best way to have things. But it suits my taste, because it makes the easiest way to do things correspond very closely with the way I like to see things done: distinct and well-distinguished layers of functional and object-oriented code, sorted according to where each is of the most utility. C#, by contrast, tends to guide you toward a foamy lather of functional and object-oriented code with no identifiable organizing principle.
I used to think the same, but having now tried FP-style libraries for serializers (Thoth.Json) and HTTP servers (Suave), I think they are far superior to imperative or OOP alternatives. The ability to design these things in a declarative style makes the code much more robust and easier to read.
But if you compare F# with C#, is not just LINQ .Select vs List.map
And i am not speaking about function, who is just a part of why i like F#, not the best one
Is the different defaults who matter, who guide you to an easier code who highlight the domain
- structural comparison vs reference comparison
- expression vs statement based
C# will catchup with some features for sure.
Lot of new C# feature remove boilerplate (file scoped namespace, etc) and is a good trend
But the core defaults will be the same.
That will not push to a single direction (if fact you have LOTS of way to write the thing in C#) while F# try to push to the same way
To do so, C# continue to add thing to the LANGUAGE.
Now is a lot more complicated than .NET 2.0
While F# try to do the same, but the language doesnt change.
An example: the async/task.
C# added the async as keyword, plus all needed to make it work, and the compiler generate special code.
F# has built that from the general computation expression feature and (recently) the state machine support.
The main downside is that its a lot to learn, and it takes a good while before you get productive. But I am absolutely more productive now in Haskell than just about anything else, save perhaps ad-hoc text munging task; the shell DSL is just so handy for random one off stuff.
Also I've only used node/elixir serverside, but aren't http servers just a huge pipeline? It fits quite well into functional programming.
Absolutely. At a certain level of abstraction they can be viewed in this way.
How do HTTP servers actually get packets to and from the machine? At some point you have to interact with the operating system. This is not a realm where functional programming is very feasible today.
These are two examples I would probably come up with if you asked me “where does FP thoroughly beat imperative?”
(De)Serializers - parser combinator libraries like attoparsec absolutely BTFO imperative parser libraries in most dimensions. Bidirectional codec libraries like haskell’s Binary, Serial, Aeson are top-tier. Functional formatters like Blaze are top-tier as well.
Haskell’s HTTP ecosystem is massively better than anything else I’ve used, and I went on a binge of trying a ton of HTTP servers like 6 years ago (in python, ruby, C++, Go, and finally Haskell).
- make composition ergonomic, idiomatic, and easy (including the usual iterator map/reduce, etc.)
- let the type system protect you (including nullability)
Then you've already got the most important strengths, in my opinion.
Seems like the most significant thing they could do.
I've spent so much time looking into using languages like Rust, Haskell, F# pretty much just because I want a compiled language with DUs. But always lose interest due to more practical requirements like tooling and package ecosystem.
If C# had them, I could stop looking. I would basically use it for pretty much everything except web stuff. And that's coming from someone who used to be very anti-MS. To me C# does everything I want in a language, except for lacking native simple DUs.
There's lots of cool things I've learnt about in FP languages. But discriminated unions are really the only feature that I feel like I'm really missing out on in other languages.
> I strongly believe that functional programming is not a good fit for 100% of software architecture. The best is some sort of hybrid.
And yeah I agree with you. FP is awesome at many things, but not everything. I went down the FP rabbit-hole, it was a and I had many revelations. But once the excitement wears off, I realised that I still like using classes for some things that are just inherently mutable... e.g. GUIs, and progress bars, SQL transactions etc.
Not to mention that being able to type `someObject.` and then get autocomplete for all its methods is a huge time saver, and something that is quite a lot more painful with pure FP where you're much more reliant on your memory and looking things up to find all the functions for something.
I think Rust did really well in balancing the two. I just want Rust's simple struct instantiation and enums in C#, and I could stop wasting time looking for the mythical "perfect language".
Haskell would be a better language in every domain where C# is applicable. That is from a language perspective, abundance of libraries is a different question, as it's a matter of the community size and attention.
// thing1 has finished
Where the functions block. Async and Monads and Futures and all are useful for some things, but mostly I just want to do stuff sequentially, blocking, and not confuse myself.
Scala allows this, although the widespread Future-ification of libraries makes it hard to actually practice.
main :: IO ()
main = do
myBusinessLogic :: IO ()
myBusinessLogic = do
doThing1 :: IO ()
doThing1 = do
putStr "Hello "
doThing2 :: IO ()
doThing2 = do
ghc -o main main.hs
[1 of 1] Compiling Main ( main.hs, main.o )
Linking main ...
If you want to talk more about it, feel free to email me.
There was a time years ago when someone was showing me the types of (>>) and (>>=) in Haskell and my question was "ok, but what does that have to do with IO?!?"
This is a really wonderful quality in a compiled language. I wish there were more languages with this attribute (but it’s a lot to ask for).
The experience is close to having an elegant Python with types, definitely worth becoming more popular.
I can read OCaml and dabbled in it, but that doesn't help me write substantially better .NET because I will be iterating over lists or graphs in a different way, handling IO (and sockets) differently, etc.
But I can see how someone conversant with C# and the .NET ecosystem would really like F# since the barrier to entry is substantially lowered, and the code is just so much nicer and tidier.
Shame there isn't a proper AOT-compiled LISP for .NET (no, Clojure doesn't count, I never managed to get the .NET version to work for me).
Functional programming is something I think one should learn cause the concepts are useful in other programming languages too. Over time what is useful in functional programming languages will be available in other languages. In fact, a lot already is. Functional programming is not an USP in my world.
When I started with C#, code bases were full of GoF patterns, if and switch statements were banned, there were hierarchies of inheritance, there were states spread out and encapsulated everywhere, static functions were banned. Today, code bases are mostly stateless except for things that needs to be reused, there are pure functions everywhere, inheritance is mostly used in libraries and where interfaces can’t be used (yet), switch is ok and pattern matching is around the corner in several languages.
More unpopular opinions. Immutability is overrated. In some programming styles, in some old languages there was a high risk of changing something by accident. Typical cause people did not know if they were working on a reference or a copy. Also, it was common to change fields on purpose. This problem does not really exist in a lot of programming languages today. But I do wonder if Go coders code benefit from some more immutability.
I think the future of F# looks very dark but the functional style of programming will for sure have its place. What could change this is if F# really excels in some area like Go have done.
I mean what is more exciting than having to deal with FactoryFactoryFactory and AbsteactFactoryVisitorBuilderProxy written by others?
Having forced OOP is bad enough by itself but people found ways to make software development even worse.
…on a substantial amount of my screen throughout the whole read.
I really like having my screen split down the middle (tmux), with code on the left (“vim code/“) and program output on the right (“watch-exec code/ — python -mcode”).
Every time I change a file in code/, watch-exec runs the program again.
Doing this with dotnet run seems to take a full 2 to 4 seconds to run though, which seems pretty heavyweight. It’s obviously not the best way to interactively edit a file of code. What is?
Made me sad when I was playing around with generative art, where you would really want to see the result of your changes in a split-second.
You can check out ".NET Interactive Notebooks" in VSCode, that executes much faster, but it doesn't do this live watch.
Even some new stuff is like that - and partially my fault, depending on the area :)
That said, some stuff that was REALLY tough to understand was ripped out recently, the "reactor queue" and incremental build system. They were central to how editor tooling works and utterly incomprehensible since they were working around the lack of a free-threaded compiler service. Now the entire compiler is free-threaded, so all the complex machinery to coordinate work is mostly deleted and/or reduced to like 50 lines of straightforward code.
- FSharp.Core library, who contains most of the types and functions you want to use
- the fsc F# compiler
- the FSharp.Compiler.Service, who is the compiler as a library used by all editors/ide, written to be performant and support the logic of IDE functionalities (find references, etc)
- The Microsoft Visual F# code, who is the F# extension for Visual Studio
So is complicated repo, but for a reason.
But nice to work with given the features there
Oh, I didn't know that.
Can I apt-get the toolchain?
[EDIT]: and for those interested, here's the github repo:
Fortunately MS provides a better alternative: the dotnet install scripts.  Make sure you add the install directory to $PATH.
 - https://docs.microsoft.com/en-us/dotnet/core/tools/dotnet-in...
Oh, this doesn't sound good at all.
One thing I positively hate about Java is always having to worry about which version of this and that (sdk, vm, headless, etc...) you run on top of. The claim of write once run anywhere has not really happened in my book.
Even C++ is better in that regard these days.
I am certainly hoping the .NET / F# environment didn't inherit that attribute from the Java ecosystem, that'd be a major downer.
My goal is only to learn F# / experiment with it, and I'm certainly hoping never to have to spend a second thinking about "versions of the runtime & sdk".
Runtime versions are something you worry about when you are actually deploying / distributing applications.
dotnet build "myproject.fsproj" --runtime linux-x64 --self-contained
...it will compile your project with dotnet bundled together. So you don't need to "install" dotnet on the destination systems running your program.
It worked on some Debian and CentOS/cpanel servers that I have (never installed dotnet on them).
That command alone gives you a whole directory of files that you need to deploy. But I think it's also possible to have to completely bundle all of it into a single file.
I think this guide tells you how to do that: https://www.hanselman.com/blog/making-a-tiny-net-core-30-ent... ... haven't tried it yet though.
It was relatively easy to figure out how to install different versions alongside each other myself after digging into it. The folder structure of the distribution archives is already setup for it after all. After getting it in place it even lets you list and switch between them haha.
The CLI hides the different sdk versions. If you run `dotnet --list-sdks` it will tell you which SDK's you have installed. If you don't have the SDK you need, when you install it it should be placed in the same directory as your other SDKs. Then the CLI will recognise the different versions.
This can all go wrong if you have multiple instances of the CLI installed. That's why I recommend using the dotnet-install scripts from the start, because then you'll only have one instance and you'll be able to manage installed versions.
Unless you mean the CLI downloading the versions itself? I see your point in this case. I think that would be a nice developer convenience. From a production engineering/devops perspective, having the install scripts separate makes it much easier to automatically install the correct runtime.
In fact, F# has been open source longer than Roslyn (the C#/VB.NET compilers) and .NET Core!
There used to be a funny split in the repos: Microsoft had their own visualfsharp repo, and there was a blessed fork which was more community and Linux friendly.
Download the Sdk, who bundle both the runtime and the C#/F# compilers (everything you need to build a program) and the `dotnet fsi` REPL
All of that is open source, developed in different repos and bundled there in a nice installer.
Support all platform and can cross compile for other OS
Please do not use Mono, because contains an old version of F# and lots of quirks to use.
The current way is .NET (was called .NET Core, renamed recently) because is cross platform by design, and eventually Mono will be merged 100% there.
Plus is tons more performant and better tooling
I can indeed.
apt-get install fsharp
This is great.
How times have changed!
Goal is not to spend a couple of hours fighting to install the stack, but to use the time learning about the language.
apt-get gives me that.
MSFT-specific stack install tool don't really sound like a no-brainer, but I'll look anyways.
Hey! Someone else saw this. Very underrated resource. Also Professor Frisby's Mostly Adequate Guide to Functional Programming needs more attention too.
Things I would add:
1. Start off with what types you need, not functions. Creating your types like the tools you need to do the job. Then build combinators. Convert one complex type to a primitive. Then a primitive to your type.
2. Once you have your types, it's not that difficult to make them functional. You just make methods for "of", or "chain", or "map", or "ap". Now you have monadic and applicative interface.
3. The functional part is just like when a shell drops to a program's prompt, like >ftp and >mysql. You setup a series of commands like that, with Either or Task or Fold. Provide input, prepare a list of commands, and then run them when everything is valid and ready.
4. Bundle size is a problem for web. Until tech improves and bandwidth prices lower, 100 KB on a high volume website is a problem. Nothing wrong with taking a proven solution, like wordpress, and caching it.
5. Property based testing is for under the hood, your types and combinators. It's not something you do with Cypress or units. All that input is type unknown, and needs validation before going to what's coverage under property based testing.
6. One fault that always occurs is trying to use Runtime encoding to build some extensible modular program. Don't do that. Types are for build. Validation is for runtime. If it doesn't validate, show a user a message. Don't try to build some hot swappable modular program, where types are read from input. If they want to program the computer, they can install the IDE.
In my mind, it's taken the easiest 80% of the features of a true functional language, but then picked a pragmatic point at which to stop; the result is a language which is easy to learn and also makes it easy to use "most" of the good things you want from a functional language.
It is a mechanism to provide type safe access to structured data and data services with almost no code. It feels like magic.
Like other ML-derived languages (F# is an OCaml dialect) it has algebraic data types and pattern matching to make code simple and complete without missing edge cases.
Also, it is arguably the best functional language on .NET, so it has a great platform with a large ecosystem to build on. And the tooling is great (Jetbrains, Microsoft IDEs).
I still need to learn how to write a type provider just so I can.
it is also the only real one. On .NET we have C#, C++ and vb.net then there are some other fringe langs like IronPython or IronRuby and I think there is a PHP one too.
- Expression orientated
- Lightweight syntax
- Emphasis on immutable data and pure functions
- do-notation (in F# these are called Computation Expressions)
- Type-safety without too many annotations (global type-inference)
- Mainstream ecosystem of libraries (in this case .NET)
It's really easy to express a domain (with discriminated union and type) and the defaults (immutability, option, removal of boilerplate, high order function to manipulate data, structural comparison not reference) allow me to write correct code, who is readable without too much friction.
Support a repl as first class, so that works too if you like repl driven development
Community is also nice
I don't work in the .NET ecosystem, but I know if came up a lot when I was getting into Erlang/Elixir.
(Sorry. Couldn't resist.)