Hacker News new | past | comments | ask | show | jobs | submit login
What I wish I knew when learning F# (danielbachler.de)
233 points by DeathArrow on Oct 14, 2021 | hide | past | favorite | 147 comments



I've had good experiences running F# on Linux. I used it to build an API generator from database schemas [0]. Similar to Go you can get a single static binary you can copy anywhere.

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 [1] 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.

[0] https://github.com/eatonphil/dbcore

[1] https://docs.microsoft.com/en-us/dotnet/fsharp/language-refe...


The language, in general, wants you to be explicit about what you're working with. So implicit access to interface members isn't a thing, you need to say that "x is this interface type" or cast.

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[0] has 3 examples that don't compile with F# 5 but will compile with F# 6.

[0] https://sharplab.io/#v2:DYLgZgzgNALiBOBXAdgHwPToAQFMAeAhgLYA...


> implicit access to interface members isn't a thing, you need to say that "x is this interface type" or cast.

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.


Implicit upcast? That's amazing, I feel like that's going to help a lot with many branched functions.


Yeah, it's a good feature. It's been done carefully -- there's room to do more of this kind of upcast conversion in the future -- but there's also a danger of creating code that behaves weirdly, too. See here for some of the dangers in Scala 2: https://latkin.org/blog/2017/05/02/when-the-scala-compiler-d...


It's worth mentioning that 4 out of 5 puzzlers in this article are fixed in Scala 3. The one that remains behaves arguably as it should.


It's also important to add F# is an inspiration for an amazing MS Research effort, F* [1].

Furthermore, it is also worth checking Dafny [2] (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 [3], and F* is also looking quite promising.

[1] https://www.fstar-lang.org

[2] https://dafny-lang.github.io/dafny/

[3] https://www.cs.columbia.edu/~junfeng/17sp-e6121/papers/ironf...


Why oh why are people still putting special characters in the names of programming language?

# . + 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.


What makes you say that F# is an inspiration for F? I recently went through the F tutorial and saw no mention of that. Obviously F* is an ML, but it seems more like Ocaml than F#.


Might be referring to the fact that quite a bit of f-star is written in F# and that F# is an emit option?

That said, the F# code that you see in the compiler source looks syntactically more like OCAML and makes little use of .NET.


Excellent illustration of 7331's sister-level comment... your two *s have turned into markup


How does Dafny compare to M#, the unreleased language used for project Midori?


> That you cannot call interface methods on an object implementing the interface without explicitly casting to the interface [1] is pretty weird.

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.

https://docs.microsoft.com/en-us/dotnet/csharp/programming-g...


at least they picked cute bird face emoji for it :>


I am building a tabletop game trainer for Star Wars FFG as the RPG Sessions app is incomplete and trying to do all of the things manually or remember every rule + every modifier to the rule + what all of my NPCs stats are is impossible, and thus when one looks things up, it slows down gameplay a lot, especially during battles. Since the whole point of the system is to make battles feel fast and cinematic, and we want to strictly adhere to the rules as written so we are not constantly re-adjudicating (read: bickering over) house rules, we need something more.

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.


Yeah, those boundaries between beautiful domain type <-> DTO <-> database is also something my team and I struggle with. In the end we're happy with just writing it out, but there is this nagging feeling that it could be more ergonomic.

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.


> 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.


Yup, I love Thoth. At work we just use System.Text.Json + FSharp.SystemTextJson though, that also generates (de)serializers, and it has a bunch of options to fine-tune naming conventions, sum type encoding, etc. Super useful and easy to hammer out the correct type for very big and nested JSONs.


I kind of like the Elixir Ecto approach where your "DTO"s are mostly plain old data with a single hidden attribute identifying the database table and some other db metadata. They're not live/active, you have to manually pass them to functions to update, but you avoid having two nearly identical types.


The biggest for me is needing to understand the .NET ecosystem without knowing C#. Then there's the lack of beginner information. I own several F# books and none of them assume you're coming from a scripting language background. I eventually gave up for good. OCaml doesn't have the .NET problem, but I found it to be unergonomic in a lot of ways.

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.


There's definitely room for a book or blog series along the lines of "F# for python programmers" or something.

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


Linking your demo of VS Code Notebooks cause it was great: https://youtu.be/_QnbV6CAWXc?t=1298


I had the exact same problem. Even all the http servers had a lot of .net boilerplate to get started. A lot of that should be abstracted. I built an http server library that abstracts the .net parts. https://wiz.run/

The hello world server:

  open Wiz.Server
  open Wiz.Context
  open Wiz.Route

  let myHandler ctx =
    ctx |> sendText "Abra Kadabra Kalamazoo!"

  let myRoutes = [
    get "/" myHandler
  ]

  genServer()
  |> setRoutes myRoutes
  |> run


thinking more about it, F# needs to appeal to non-dotnet developers and other developers looking for a functional language for real workloads.

F# currently seems easiest for c# dev to get started, but c# devs have almost no incentive to do so.


I could say the same thing about any language. For instance:

"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.


I was thinking as a conscious strategy that would impact the way all libraries are built. In my example I posted above, there are no dotnet APIs showing. Its all abstracted. Libraries should create new APIs that are just wrappers around the dotnet library.

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

  open System
  open Microsoft.AspNetCore.Builder
  open Microsoft.AspNetCore.Hosting
  open Microsoft.Extensions.Hosting
  open Microsoft.Extensions.Logging
  open Microsoft.Extensions.DependencyInjection
  open Giraffe

  let webApp =
      choose [
          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
          app.UseGiraffe webApp

  [<EntryPoint>]
  let main _ =
      Host.CreateDefaultBuilder()
          .ConfigureWebHostDefaults(
              fun webHostBuilder ->
                  webHostBuilder
                      .UseStartup<Startup>()
                      |> ignore)
          .Build()
          .Run()
      0


You have to learn some of .NET framework if you plan to use its features like ASP .NET, but you do not have to learn C#.

If you plan to use Python you have to learn some Python libraries.


I get that you have to now. my point is that you shouldn't have to. lot of it could be abstracted. the more that is abstracted then the lower the barrier for newcomers.


Using Python doesn't require you to learn a (big) second language before you can do anything useful, though. F# arguably does. Maybe that is the best F# that F# can be. Which, for a lot of people, is not good enough.


I am a C# developer and I find F# very interesting. I love the functional paradigms in C# and I want to be able to use more of a functional style.

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.


This was my issue when trying to learn clojure as well


Yes, my experience as well. Clojure is more "lisp for java programmers" than "java for lisp programmers".

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.


That is the issue with any guest language, because it is impossible to hide the platform, unless the language is so constrainted that is practically useless.

It is a hard to swallow truth, but guest languages really require understanding of the underlying platform.

Another example, no matter one's opinion on C or JavaScript, mastering them is a much more confortable life on UNIX/POSIX platforms and browsers than trying to pretend they aren't there.


Agreed. I think you say it more succinctly.

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.


First off -- I don't really want to get into bickering about Java is OO or not.

That said:

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.


I have two remarks here: even if some procedural languages like C seem boiler plate-ish by requiring you to write everything instead of having a platform which offer a high abstractions that has two huge advantages: you are in absolute control and you understand everything from how the bits are layed out in memory and system calls to higher level business logic. So if you need to change something, add a feature, improve performance you know exactly where you have to intervene. Encapsulating state is very bad on the large scale because you have hidden state in lots of places and you can't argue about what a program does, how it does it and how to modify it. You have to write lots of tests to make sure things won't broke.

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.


I would argue that, if the scope of impact on a change is that large, then, by definition, you have not encapsulated your implementation.

(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.


Then a C compiler optimizer decides that knows better and changes the table underneath, or maybe it doesn't but the Intel microcode unit decides to write the Assembly instructions in unexpected way.

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.


Same. F#, Clojure, Scala, & Kotlin have all been the same for me. Python's VM is written in C, but I have no reason to know C. In Clojure they have you call out to Java libraries a ton. Someone always tells me that this isn't true on here, but it was my experience as well. Having to learn so many tools like emacs or CIDRE or Leinegen was also a turn off.


I really love Clojure, but I have a lot of experience on the JVM so I didn't have to climb multiple learning curves simultaneously.

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.


There are multiple options to approach the Clojure* ecosystem. But yes, it is more focused towards dedicated and experienced developers overall. I use IntelliJ Idea + Cursive for development and that seems quite comfortable. I have a very rough sketch of the environment setup for a real app here: https://www.orgpad.com/s/hfxYQYkcLYV We use a similar setup to develop OrgPad itself.

You can use Babashka https://github.com/babashka/babashka for a quick and dirty setup to get you up and running quickly. That is also useful for scripting some things. You might also try shadow-cljs + ClojureScript, if you are more at home in the Node.js/ Browser JavaScript ecosystem and want to do something with it. There is a new "(Not)Babashka" but built on ClojureScript: https://github.com/borkdude/nbb that might be interesting as well.

I have found this video by James Trunk: https://youtu.be/C-kF25fWTO8?t=848 to have a very nice live coding example.


Thanks for the links, neighbor.

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.


Clojure is pretty cool, but I've come to understanding that as a beginner I would have been better off just learning good old Lisp.


Yeah, even as a veteran java programmer, I could really only get clojure after mastering a scheme (racket).

I highly recommend 'The Little Schemer' if someone wanted to tread the same ground. Such a strange and fun little book.


Haskell doesn't call out to anything.


I'm surprised, I found clojure even while I was a pretty much total beginner programmer fairly easy to get into, via clojure for the brave and true at least was very good for getting the basics.


Clojure for the brave and true shows the exact problem.

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.


Even that involves downloading and installing a fair amount of build tool stuff which may be necessary, but just drove me away.


Kinda agree, but F# works much better for me with my knowledge of .NET than e.g. Clojure with my understanding of Java.

It's a cliche saying, but for me F# just works! (tm)


Ah, that's interesting.

I'm a huge fan of F#, but I had the advantage of knowing .NET first.


There are several good books like: https://www.amazon.com/Stylish-Crafting-Elegant-Functional-C... and https://www.amazon.com/Get-Programming-guide-NET-developers/... and also some courses on Udemy like: https://www.udemy.com/course/fsharp-from-the-ground-up/ and https://www.udemy.com/course/hands-on-f-application-developm...

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.


I'm not saying that you can't write an F# script, but that the path to getting one written is not obvious to those coming from scripting languages that are much simpler than the VAST .NET & JVM ecosystems. There is always some bizarre .NET arcana that gets in the way.


Sometimes that's true but I've found as soon as I need packages things get complex. I don't find F# that hard scripting wise; at times found it easier than some other languages especially when I need to import libraries into my script. Instead of needing to install a system wide pip package, set up a project with Maven/Gradle, etc around the script, etc. With F# and the like you just do something like:

#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-...


If you want to dabble in functional, C# is actually a compelling option now. Switch expressions and LINQ can take you a long way.

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.


I agree that 100% functional is not necessarily the way to go. That said, I am a big fan of F#'s "functional first" ethos, which drives you toward that sweet spot of a functional core inside of an imperative shell.

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.


This is the same issue I have with Javascript. If you don't have to use something, you just won't, or maybe I'm just lazy.


The creator of LINQ, Erik Meijer, wrote a fun article "The curse of the excluded middle: Mostly functional programming does not work". Yes it's tongue-in-cheek and highly provocative, but many of his points are true. There is a lot more yet to be learned from functional programming than LINQ and switch expressions.

https://queue.acm.org/detail.cfm?ref=rss&id=2611829


This is such a weird article. All of his examples are insane strawman non-idomatic C# code, and then he complains when they don't do whatever he decides "an average programmer" would expect them to do. Huh? They were all either doing exactly what I expected them to do (silly things), or were so strange and alien that even Visual Studio has no idea what to do with them! I've never seen anything like that Cell<T> example and it doesn't come close to compiling. What a weird, weird thing to do: write code that literally does not compile and then complain about it!?


Is it meant to be tongue and cheek? I personally didn’t get that impression. I assumed he was using the non-religious meaning of “fundamentalist”: strict and literal adherence to a set of principles.


I mean it has a deliberately highly-provocative style. But the points he raises are all completely valid.


> The serializers, http servers, etc. are probably not worth the squeeze to force into a functional domain.

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.


+1


FP is better on a 30 year timeframe but on a next-quarter timeframe probably still has not "crossed the chasm" – you need not just something accessible like ZIO but you need to figure out what it's killer app is, which the FP community currently doesn't have a clue. "HTTP server but more declarative and made with monads" is not something people cannot live without


I think this depends on the relative weights you assign to learning a new language and program correctness.


Sure - C# is the best functional language, while Haskell has already been the best imperative language for a while now [0].

[0] https://stackoverflow.com/questions/6622524/why-is-haskell-s...


C# switch are just syntactic sugar. nice for sure.

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 - immutability

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.


If they keep adding features to C# I hope it won't become the Frankenstein monster C++ is.


I initially believed this, but I have come to realize that pure functional programming is transformative for both business logic and infrastructure. Its a lot easier to get started in the business logic side; dealing with effects in the context of pure functions is a lot to figure out, I don't even know if you really can do it in not-haskell. But, via Haskell I have been able to achieve far greater levels of producitivty in these tasks than ever before.

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.


I think serializers would be better off as railways, allowing you to combine small serializers while also collecting the errors so you end up with a conclusive outcome. Haskell's parser combinators are quite fun for example.

Also I've only used node/elixir serverside, but aren't http servers just a huge pipeline? It fits quite well into functional programming.


> aren't http servers just a huge pipeline

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.


> The serializers, http servers, etc. are probably not worth the squeeze to force into a functional domain.

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[1] 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).

[1] https://github.com/lovasoa/bad_json_parsers


I would have to respectfully disagree with you on the serializers part here. For me, after decades of fighting with “magic” stuff, simplicity is the key feature. So, everything that is a simple map function in disguise I tend to stick with functional languages and approaches.


I think it's really hard to properly define "functional programming" concretely, but I'll partially agree. If all a language takes from FP is:

- 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.


Functional programming is not just programming with closures. It's about "referential transparency". Referential transparency permits higher-level mathematical reasoning, which leads to safer/better program composition, optimisations, caching/reuse and parallelisation/concurrency. If you are willing to concede mutable-state and other uncontrolled effects, there is much to be gained.


You might mean "enforced referential transparency" by having immutable variables and no I/O. Otherwise most languages allow you to have pure functions and nothing forces you to mutate state.


Citation needed.

<ducks>


I really wish they put more priority on bringing discriminated unions into C#.

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".


You may be interested in (for example) the approach taken with Halogen, the PureScript web framework. Every widget on the screen has its own state and actions, widgets send each other messages, etc. It ends up being very "object oriented" in a way that you're suggesting, but it also is very principled, i.e. components send each other messages, do not have access to each others internal states, etc. This gives you a really good way to separate concerns, while having each "widget" have its own state/not having to have a single global state a la elm.


> I strongly believe that functional programming is not a good fit for 100% of software architecture. The best is some sort of hybrid.

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.


I think enforced purity is problematic (although obviously useful for some purposes such as program correctness proofs). I still want to be able to write:

doThing1();

// thing1 has finished

doThing2();

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.


Haskell allows this too, users can just code everything within IO until they feel comfortable with adding extra purity through refactoring:

    main :: IO ()
    main = do
        myBusinessLogic
    
    myBusinessLogic :: IO ()
    myBusinessLogic = do
        doThing1
        doThing2

    doThing1 :: IO ()
    doThing1 = do
        putStr "Hello "

    doThing2 :: IO ()
    doThing2 = do
        putStrLn "World"

    ---
    ghc -o main main.hs
    [1 of 1] Compiling Main             ( main.hs, main.o )
    Linking main ...
     ./main
    Hello World


True. Perhaps my issue is that I know how that's implemented and my brain can't see it the same as sequential statement execution ;)


This is a function of familiarity though. Get familiar with it and you'll understand it just fine.

If you want to talk more about it, feel free to email me.


I think dboreham's objection was exactly the opposite!


Idk i mean simply knowing what the code for >> looks like doesn't mean you're used to thinking in that way.

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?!?"


Lack of discriminated unions and single case unions make C# a no go if you want to fully scratch the functional itch


> It has a scripting mode where compilation is done on the fly. This makes it look and feel very similar to python but you still get type checking and much better performance.

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).


That feature was actually inherited from Ocaml. Both have a REPL and type inference powerful enough to insure that you never indicate types in Ocaml and rarely in F#.

The experience is close to having an elegant Python with types, definitely worth becoming more popular.


The only thing F# has in common with Python is that it is a simple language. Other than that they are different paradigms.


Pretty much on point. I've been building a .NET, bootstrapped webapp (i.e., no Razor, no "cargo culting" so I can actually understand things from the ground up) piecemeal, every other weekend, in both C# and F#, and, like Java before it, _understanding the ecosystem and libraries_ is critical.

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).


F# has become my default goto language. It is absolutely amazing to create cloud services in it an I have a small ETL tool as well. I would not trade it for anything else.


In my area there was a huge push for functional programming and F# some years ago. A long time ago since I heard anybody working with F#. Clojure is somewhat popular though in fintech.

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.


After null, the worst calamities that hit the software world are Uncle Bob's principles and GoF patterns.

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.


This is off topic. I can't resist - that's the thickest sticky header I've seen from a developer.


Yes, hah, I too was impressed with the chutzpah of having…

“DANIEL”

…on a substantial amount of my screen throughout the whole read.


There was a recent thread about Algebraic Effects in OCaml [0]. Does F# have something similar?

[0]: https://news.ycombinator.com/item?id=28838099


It lacks many things that OCaml has. On the other hands, units support is awesome, I wish there would have been more attention to that, and more popular in other languages as well.


No. It has lightweight syntax ("computation expressions") you can use to sugar monads, but no effects system.


The main thing I found strange about F# was that, having prior familiarity with Ocaml and SML/NJ, was that what F# calls modules are completely unlike the module system of any other ML-inspired language, being instead an adaption of C# classes to serve F#'s namespacing needs. Since the module system is of similar importance to code reuse to Haskell's typeclasses,the relative deficiencies of F#'s module system hurt.


His claimed inspiration, Hillel Wayne’s post “Why Python is my Favorite Language”, is a piece of fluff that basically says "I only like the things I already know". I was so uninspired, I couldn't read further.


You fault an article for being superficial while yourself only giving a superficial read? I hope you see the irony and reconsider.


What’s the best way to play with F# that feels natural for a Python programmer?

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?


Yes! Those couple of seconds wait-time are a big bummer for that style of fast-feedback coding (for which there is the built-in `dotnet watch run` by the way).

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.


Yeah I think 'dotnet fsi {fileName}' with a script file works. Also Ionide with VS Code using Alt+Enter (similar to other language VS Code repl's) inside a *.fsx script file to run the highlighted code in a F# REPL. F# Interactive has doco online as well that isn't that long but covers most things (single commands, script files, loading packages, etc).


The obvious solution is not to compile your project with every change. See F# interactive: https://www.jetbrains.com/help/rider/FSharp_interactive.html


Link to What I wish I knew when learning Haskell is broken because it has https. The correct link is http://dev.stephendiehl.com/hask/ in case anyone was looking for it.


I like F# quite a bit, but the main selling point for me besides my algorithm stuff would be data science/ML. Sadly, the F# libraries like the R Type Provider, JSON Provider, etc. don't seem to work.


When I learn Haskell, I'll be lot closer to God.


God uses assembly.


Molecular biology is implemented in perl.


Is there a FOSS compiler for F#?


You can simply contribute to https://github.com/dotnet/fsharp/, the maintainers are very friendly to new contributors in my experience (though the codebase is old and often quite hard to understand).


> though the codebase is old and often quite hard to understand

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.


As a note, the codebase is also huge because contains lot of 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


the Microsoft .net core compiler is FOSS, but if that doesn't count there is also mono.


> the Microsoft .net core compiler is FOSS

Oh, I didn't know that.

Very cool.

Can I apt-get the toolchain?

[EDIT]: and for those interested, here's the github repo:

https://github.com/dotnet/fsharp/


You can, but I wouldn't. The dotnet CLI contains its own version manager, so you need to have more precise control over the versions of the runtime & sdk you have installed. You can have multiple versions installed simultaneously, dotnet then chooses the runtime based on the project. IME linux package managers mess this up bad.

Fortunately MS provides a better alternative: the dotnet install scripts. [0] Make sure you add the install directory to $PATH.

[0] - https://docs.microsoft.com/en-us/dotnet/core/tools/dotnet-in...


> so you need to have more precise control over the versions of the runtime & sdk you have installed

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".


If you just want to learn, install the latest version of the SDK (.NET 6, currently in RC) and it will be able to build code or reference libraries from every older framework version.

Runtime versions are something you worry about when you are actually deploying / distributing applications.


I did some tinkering with F# this week, and found out you can use this command (even from a Windows desktop):

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's a shame the CLI doesn't operate more like nvm or sdk man allowing you to download different sdk versions.

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.


That's exactly how the CLI operates though? Maybe I didn't explain it well enough.

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.


> Oh, I didn't know that.

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.


See https://dotnet.microsoft.com/download

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


Yes, you can.


> Yes, you can.

I can indeed.

    apt-get install fsharp
(although it pulls in 300MB of dependencies :( )

This is great.

How times have changed!


I wouldn't do this, as this is the mono bundle of fsharp. The blessed/supported way to go now is to install the dotnet-sdk packages[1] and then use `dotnet fsi` for interactive work, and `dotnet build/run/etc` for project-based work in F#.

1: https://docs.microsoft.com/en-us/dotnet/core/install/linux-d...


I'll give it a shot, but TBH, I like the no-brainer apt-get way.

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.


As a fellow linux user, my package manager does the job for both the runtime and the sdk. It should work for yours too.


... to be clear, Mono does not provide F#. Microsoft releases F# for Mono.


F# and .net core are completely open source. You don't need mono.


I wish I have Elixir with types like in F#


I wish F# had a project comparable to Phoenix to boost its visibility.


It has Giraffe, SAFE stack, Fable, Bolero.


> Scott Wlaschin’s fsharp for fun and profit is one of the best functional programming resources I know across all functional programming languages.

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.


What does F# do that other languages don’t? What it’s unique benefit?


It's just really pragmatic about the upsides of functional programming. It describes itself as "functional-first", but isn't afraid to fall back on OOP in situations where OOP really does model the situation better.

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.


Type providers are novel.

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 haven't done as much as I would like with them, but I feel like until someone tries type providers they don't get just how powerful they are. Being able to point to an example .csv file and have it generate the classes for you, and update them if you change said format, in a way that it ensures the types still work, is amazing. And you can do it for ANYTHING as long as you have a way to extract the data to feed it to the compiler.

I still need to learn how to write a type provider just so I can.


> it is arguably the best functional language on .NET,

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.


I forgot Boo and Nemerle. They are quite niche languages though.


It's the combination of features which is killer. I don't know of another language that offers all of these:

- 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)

- Compile to JavaScript


Units of measure is also very cool!


For me is that pratically every time, if compile, it works.

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 believe it's the functional language option on top of .NET. From what I understand, it's a pretty well designed one too.

I don't work in the .NET ecosystem, but I know if came up a lot when I was getting into Erlang/Elixir.


Functional, readable, easy to learn, as performant as C#, has the benefit of huge .NET ecosystem of libraries.


That it's the same as Gb (on a piano or fretted instrument)?

(Sorry. Couldn't resist.)




Consider applying for YC's Spring batch! Applications are open till Feb 11.

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

Search: