Hacker Newsnew | past | comments | ask | show | jobs | submit | 110bpm's commentslogin

I put together a quick-start guide to F# Computation Expressions — showing how you can go from C# async/await all the way to Result<> workflows with let!... and!... expressions, and even a custom validation {} CE. [0]

This is a practical side of F# that doesn’t get enough spotlight — but one I’m using daily.

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


That was a great read. Thanks for sharing.


`async` is F#'s original implementation of async programming. It is the precursor to C#'s await/async.

`task` targets the .NET TPL instead, which is also what C#'s await/async and all of .NET *Async methods use.

While the `async` implementation still offers some benefits over `task` (cold vs. hot starts [0]), my advice is - if you're doing backend code on .NET, you should use task. The tigher integration with the .NET ecosystem & runtime results in better exception stack traces, easier debugging and faster performance.

[0] https://github.com/TheAngryByrd/IcedTasks?tab=readme-ov-file...


This isn't such a big issue in my experience. Auto-formatting helps a lot, the code needs to be just syntactically correct.

The default F# autoformatter is bundled/supported by VS Code, VS and Rider [0].

[0]: https://fsprojects.github.io/fantomas/docs/end-users/StyleGu...


I wanted to learn more about where this claim comes from and found this article [0].

The linked commentary [1] raises several interesting points.

"Unfortunately, the authors did not provide details of their BLAST research. The patent database that they used contained 24,712 sequences. Yet, by querying BLAST, we obtained a database of 46,121,617 patent sequences with an average length of 560 nucleotides. The authors should give more details and justification for their query, especially if they queried the full database but a posteriori restricted their computation. Of note, with such a large database, and despite the fact that the average sequence length decreased, the probability of finding at least one sequence containing one of the 16 patterns previously mentioned may rise to 68% under assumption 2."

0: https://www.frontiersin.org/journals/virology/articles/10.33...

1: https://www.frontiersin.org/journals/virology/articles/10.33...


FTA:

The absence of CTCCTCGGCGGGCACGTAG from any mammalian or viral genome in the BLAST database makes recombination in an intermediate host an unlikely explanation for its presence in SARS-CoV-2. You can verify for yourself that there are no other sequences with an exact match.

The subsequent comment about statistics of blast searching is irrelevant.


So the conclusion of the comment-article cited in the parent of your comment is false?

"According to the current phylogeny, FCS appeared independently six times in the Betacoronavirus lineages, demonstrating that FCS insertion is compatible with natural evolution (2, 7, 8). [...] 7. Wu Y, Zhao S. Furin cleavage sites naturally occur in coronaviruses. Stem Cell Res (2021) 50:102115. doi: 10.1016/j.scr.2020.102115"


it's incompatible. Why is there COVID speculation in Stem Cell Res?


"Unfortunately, the authors did not provide details of their BLAST research. The patent database that they used contained 24,712 sequences. Yet, by querying BLAST, we obtained a database of 46,121,617 patent sequences with an average length of 560 nucleotides."

is that not relevant?


just do the BLAST yourself and see. it's a publically available database. you will find zero other exact matches. note that today in 2025 there are a shit tonne of sequences that are resequencing of covid variants so you'll have to filter those out

https://blast.ncbi.nlm.nih.gov/Blast.cgi


Given we are on HN, it seems more likely that the claim originated on substack. https://news.ycombinator.com/item?id=29938732 this may be one of the progenitor threads.


I wanted to write an approachable introduction to the F# Computation Expressions + Results programming style. Rust users may recognize many similarities. I find F# great for writing software on .NET, yet many of its unique features aren’t well known outside the community.

The motivation for this post grew out of a HN comment thread [0].

I'd be interested to know what you think. Feedback and ideas are appreciated!

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


For me, the closest language currently is F#.

The open-source ecosystem is not as massive as Go's or the JVM's, but it's not niche either. F# runs on .NET and works with all .NET packages (C#, F#, ...). If the .NET ecosystem can work out for you, I recommend taking a closer look at F#.

F# allows for simple code, which is "functional" by default, but you're still free to write imperative, "side-effectful" code, too.

I find this pragmatic approach works extremely well in practice. Most of my code ends up in a functional style. However, once projects grow more complex, I might need to place a mutable counter or a logging call in an otherwise pure function. Sometimes, I run into cases where the most straightforward and easy to reason about solution is imperative.

If I were confined to what is often described as a "pure functional" approach, I'd have to refactor, so that these side-effects would be fully represented in the function signature.

F# ticks the enums/ADTs/pattern box but also has its own interesting features like computation expressions [0]. I would describe them as a language extension mechanism that provides a common grammar (let!, do!, ...) that extension writers can target.

Because of this, the language doesn't have await, async or any other operators for working with async (like C# or TS). There's an async {} (and task {}) computation expression which is implemented using open library methods. But nothing is preventing you from rolling your own, or extending the language with other computation expressions.

In practice, async code looks like this:

  let fetchAndDownload url =
    async {
        let! data = downloadData url // similar to C#: var data = await downloadData(url);

        let processedData = processData data

        return processedData
    }
I often use taskResult{}/asyncResult{}[1] which combine the above with unwrapping Result<>(Ok or Error).

Metaprogramming is somewhat limited in comparison to Scala or Haskell; but still possible using various mechanisms. I find that this isn't a big issue in my work.

IDE-wise, JetBrains Rider is a breeze to work with and it has native F# support. There is also Visual Studio and VS Code with Ionide, which are better in some areas.

You can run F# in Jypiter via .NET Interactive Notebooks (now called "Polyglot Notebooks" [2]). I haven't seen this mentioned often, but this is very practical. I have a combination of Notebooks for one-off data tasks which I run from VS Code. These notebooks can even reuse code from my regular F# projects' code base. Over the past years, this has almost eliminated my usage of Python notebooks except for ML work.

[0]: https://learn.microsoft.com/en-us/dotnet/fsharp/language-ref...

[1]: https://github.com/demystifyfp/FsToolkit.ErrorHandling?tab=r...

[2]: https://marketplace.visualstudio.com/items?itemName=ms-dotne...


Do you know of or have any shareable (sample) projects implemented in your way of doing F#? It sounds very intriguing to me


While the libraries and techniques I mentioned above seem to be well-known, I couldn't find a good public sample project.

I can recommend https://fsharpforfunandprofit.com/ as a starting point.

If there's interest, I can split some of my code into stand-alone chunks and post my experience of what worked well and what didn't.

I wanted to share some thoughts on here on what brought me to F#. Maybe this can serve as a starting point for people who have similar preferences and don't know much about F# yet.

A big part that affects my choice of programming language is its type system and how error handling and optionality (nulls) are implemented.

That "if it compiles, it runs" feeling, IMO, isn't unique to Rust, but is a result of a strong type system and how you think about programming. I have a similar feeling with F# and, in general, I am more satisfied with my work when things are more reliable. Avoiding errors via compile-time checks is great, but I also appreciate being able to exclude certain areas when diagnosing some issue.

"Thinking about programming and not the experience" the author lamented in the blog post appears to be the added cost of fitting your thoughts and code into a more intricate formal system. Whether that extra effort is worth it depends on the situation. I'm not a game developer, but I can relate to the artist/sound engineer (concept/idea vs technical implementation) dichotomy. F#'s type system isn't as strict and there are many escape hatches.

F# has nice type inference (HM) and you can write code without any type annotations if you like. The compiler automatically generalizes the code. I let the IDE generate type annotations on function signatures automatically and only write out type annotations for generics, flex types, and constraints.

I prefer having the compiler check that error paths are covered, instead of dealing with run-time exceptions.

I find try/catches often get added where failure in some downstream code had occurred during development. It's the unexpected exceptions in mostly working code that are discovered late in production.

This is why I liked Golang's design decisions around error handling - no exceptions for the error path; treat the error path as an equal branch with (error, success) tuples as return values.

Golang's PL-level implementation has usage issues that I could not get comfortable with, though:

  file, err := os.Open("filename.ext")
  if err != nil { return or panic }
  ...
Most of the time, I want the code to terminate on the first error, so this introduces a lot of unnecessary verbosity.

The code gets sprinkled with early returns (like in C#):

  public void SomeMethod() {
  if (!ok) return;
  ...
  if (String.IsNullOrEmpty(...)) return;
  ...
  if (...) return;
  ...
  return;
  }
I noticed that, in general, early returns and go-tos introduce logical jumps - "exceptions to the rule" when thinking about functions. Easy-to-grasp code often flows from input to output, like f(x) = 2*x.

In the example above, "file" is declared even if you're on the error path. You could write code that accesses file.SomeProperty if there is an error and hit a null ref panic if you forgot an error check + early return.

This can be mitigated using static analysis, though. Haven't kept up with Go; not sure if some SA was baked into the compiler to deal with this.

I do like the approach of encoding errors and nullability using mutually exclusive Result/Either/Option types. This isn't unique to F#, but F# offers good support and is designed around non-nullability using Option types + pattern matching.

A possible solution to the above is well explained in what the author calls "railway oriented programming": https://fsharpforfunandprofit.com/posts/recipe-part2/.

It's a long read that explains the thinking and the building blocks well.

The result the author arrives at looks like: let usecase = combinedValidation >> map canonicalizeEmail >> bind updateDatebaseStep >> log

F# goes one step further with CEs, which transform this code into a "native" let-bind and function call style. Just like async/await makes Promises or continuations feel native, CEs are F#'s pluggable version of that for any added category - asynchronicity, optionality, etc..

With CEs, instead of chaining "binds", you get computation expressions like these: https://demystifyfp.gitbook.io/fstoolkit-errorhandling/fstoo... https://demystifyfp.gitbook.io/fstoolkit-errorhandling/fstoo...

Everything with an exclamation mark (!) is an evaluation in the context of the category - here it's result {} - meaning success (Ok of value) or error (Error of errorValue). In this case, if something returns an Error, the computation is terminated. If something returns an Ok<TValue>, the Ok gets unwrapped and you're binding TValue.

I have loosely translated the above example into CE form (haven't checked the code in an editor; can't promise this compiles).

  let useCase (input:Request) =
   result {
      do! combinedValidation |> Result.ignore
      // if combinedValidation returns Result.Error the computation terminates and its value is Result.Error, if it returns Ok () we proceed
      let inputWFixedEmail = input |> canonicalizeEmail
      let! updateResult = updateDatabaseStep inputWFixedEmail // if the update step returns an Error (like a db connection issue) the computation termiantes and its value is Result.Error, otherwise updateResult gets assigned the value that is wrapped by Result.Ok
      log updateResult |> ignore // NOTE: this line won't be hit if the insert was an error, so we're logging only the success case here
      return updateResult
   }
In practice, I would follow "Parse, don't validate" and have the validation and canonicalizeEmail return a Result<ParsedRequest>. You'd get something like this:

  let useCase input =
   result {
      let! parsedUser = parseInput input
      let! dbUpdateResult = updateDatabase parsedUser 
      log dbUpdateResult |> ignore
      return updateResult
   }

  let parseInput input =
   result {
      let! userName = ...
      ...
      return { ParsedRequest.userName = userName; ... } // record with a different type
   }
This setup serves me well for the usual data + async I/O tasks.

There has been a range of improvements by the F# team around CEs, like "resumable state machines" which make CEs execute more efficiently. To me this signals that CEs are a core feature (this is how async is supposed to be used, after all) and not a niche feature that is at risk of being deprecated. https://github.com/fsharp/fslang-design/blob/main/FSharp-6.0...


Thanks so much for your detailed reply. This looks very cool indeed. I've had a couple tiny projects in F# in the past that never went anywhere, but you're describing essentially all the parts in a programming language that I want, early returns, binds/maps, language support for these features, defining your own keywords (not really but kinda with your expressions)

Excited to try this out


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

Search: