31 comments

[ 4.1 ms ] story [ 79.0 ms ] thread
Nice article.

F# can be so readable, e.g.:

   // WishList -> Async<Reservation>
   let workflow (wishlist: WishList) = async {

      // 1. Find matches for each wish 
       let! matches = 
           wishlist.Wishes
           |> List.map findMatchingGift
           |> Async.Parallel

       // 2. Pick one product from the combined list of matches
       let gift = pickGift (List.concat matches)
    
       // 3. Register and return the reservation
       let reservation = { Kid = wishlist.Kid; Product = gift }
       do! reserve reservation
       return reservation
   }
   view raw
   durable-fsharp
Could basically be how you would write pseudocode.

Side note: How do I stop sites like hackernoon blocking my back button? It's so annoying!

What I think is most interesting is that the thing that enables F# code to be so readable is exactly the thing that I thought would be its biggest readability problem when I first encountered it: The function call syntax and semantics. Specifically, the fact that there's no requirement to place any brackets around an invocation or its argument list, and the currying by default.

But the same syntax is what makes partial application work so well, which is in turn what makes the pipeline operator (|>) work so well.

Also, the syntax does a good job of encouraging you to organize code for better readability. It's a lot like lisp's function call syntax, only it somehow makes me feel incentivized to capture intermediate values in let statements instead of nesting my function calls. I'm guessing it's because there's something vaguely pleasing about getting to skip the parens.

I also like that F#'s version of do blocks requires you to explicitly state what kind of computation expression you're using.

I agree. The first time I saw F#, I was immediately concerned about single spaces without any braces/brackets/parens indicating function executions.

I stuck it out with F#, though, and now I miss that syntax greatly when I have to use other languages. It's surprisingly wonderful (at least in my opinion). Or at least in languages where partial application / currying / composition is easily-available. I suspect that syntax wouldn't be very helpful with languages where that isn't the case.

I actually completely disagree about the explicit `do` blocks; as a consequence of that decision, you can't write functions that abstract over all monads, at least not easily. I like F#, but that has always been my main gripe for the two years that I did F# full-time.

I realize that for business code that that isn't a super big deal, but it becomes an issue if you want to write generic libraries; the closest thing that you can do is to exploit static-resolved types with member constraints and do explicit binds.

For example, if I wanted to have a monad transformer for Async<Option>, I could define the custom monad, but then I end up having to wrap every regular Async function manually (or do a lot of manual unboxing) to make it work. A lot of the time, my logic in the async blocks isn't specific to async, and I feel that when you're forced to write custom unboxing everywhere, that's a good way to make a lot of mistakes.

EDIT: Just an FYI, despite this complaint, I do really like F#...I find it an incredibly practical language, and one of the best of the Hindley-Milner-style functional languages.

It's been a while, but, IIRC, the issue there is that computation expressions in F# are more a way of binding functionality to the syntax in an explicit way, than a direct expression of monads (or applicative functors, or whatever). F# doesn't even have a monad type to abstract over in its standard library in the first place. So what you might want to do instead is define your own library for working with abstractions, and then supply a computation expression or two for binding the syntax to the types defined in that library.

But yeah, you'd still be stuck writing wrappers to mate any pre-existing types to the library. I'm not sure that's really even computation expressions' fault, so much as an inevitable implication of F#'s lack of any facility for ad-hoc polymorphism.

While I agree that that's probably true behind the scenes, to define a new computation expression you still have to define the haskell-style "bind" and "return" functions, at least giving the impression to new F# engineers that it has some kind of generic monad type.
We use the asyncMaybe CE extensively in the Visual Studio tooling for F# and although it's a little bit of boilerplate, in practice we don't need to lift an Async<'t> into an Async<'T option> very often. The worst example of this is probably this file: https://github.com/Microsoft/visualfsharp/blob/8ea71dd368401...

In most cases the APIs are uniform enough to where it's not a pain point.

My complaints are actually in the opposite direction, though I realize that I didn't explain this very well; I wanted to be able to use an Async<'T option> in already-existing async functions that weren't doing anything specific to async. A lot of my async stuff was just doing let! to "unbox" and returning at the end.

In Haskell land I would have used something like (Monad m) => m a -> m b, and then I could throw any monad transformer into there.

I went through a phase where I tried making the entry point of each project or major module read like nothing more than:

inputs |> step1 |> step2 |> step3 |> etc

... and it seemed mostly doable, although I would wind up needing a layer of parameter rearrangement functions to make it read quite that cleanly. I don't know if I have a concrete opinion on how it worked out - I suppose I need to get back into doing this some more.

I suspect that if I were to try to hide an async workflow behind such an arrangement that the parameter-rearrangement layer might create a greater cost than a benefit.

But still, it's really neat that the language allows for this sort of attempt.

With some combinators you can indeed do this for any expressions, haskell has a tool that does this transformation automatically http://pointfree.io

This can quickly become unreadable but used well it can hide some noise and meaningless variable names.

This is why I love Haskell. You can easily define your own "|>" operator:

(|>) :: a -> (a -> b) -> b x |> f = apply x f

but nobody uses it because "function composition" reads the other way and tends to be preferable i.e.

  (g ∘ f)(x) = g (f(x))    // math

  (g . f) x  = g (f x)     // haskell

   g   f  x    g  f x      // see how the g f x order always matches with the default application and composition notation.
you can mess with the order and "turn the order of application "inside-out" but then you have to switch modes from left-to-right and right-to-left more often. Or you can try to write in a style that prefers right-to-left.
If the left to right order is preferred, Haskell has Control.Category.(>>>)

    f >>> g = g . f
Works for any instance of Category.

    g f x 
would not be the same as

    (g . f) x
you need the right associative function application operator $, ie.

    g $ f x
or simply

    g (f x)
i know that, i was just highlighting the appearance of the order of the letters without considering associativity.
I got used to this in Haskell, but I have to say that Elm's solution is pretty elegant:

  Elm:               Haskell:

  f <| g <| x   ==   f $ g  $  x    -- application

  f << g        ==   f . g          -- composition

  x |> g |> f   ==   x & g  &  f    -- reverse application

       g >> f   ==       g >>> f    -- reverse composition
It's nice to be able to change between composition and application by changing between | and < in the operator. I've actually done this quite a lot when refactoring code in Elm lately, there's something visual and intutive about it that I like. It makes it easier to mix left-to-right and right-to-left in the same code, while keeping it obvious what's going on.

I've found that certain parts, that scan/read better as imperative steps (think chains of List.map, Maybe.map, <MonadlikeModule>.andThen etc.) often benefit from a reverse application and/or composition. Sort of to simulate the look of do notation since Elm doesn't have that. Smaller functional parts are better with regular application/composition. With the directional operators, this becomes quite ergonomical.

Probaly helps that I use (and enjoy, ymmv) a font with ligatures for these symbols.

It feels so weird that I can understand this discussion now.

I used to think that all these Haskell discussions were garbage. But now I get it.

It's still very inaccessible, in my opinion. But I'm working on something to help change all that.

It is pretty much as easy, and in my opinion more readable to do that in F#

  let (|>) x f = f x
I've been wondering if there's any value in having a syntax for partial application that works more like:

  fun(arg1, _, arg3)
where the "_" indicates a skipped argument. My thought being that, in return for being a little bit more syntax-heavy, you'd get several potential advantages: No need for parameter rearrangement functions, easier overloading, and the more explicit syntax might help with maintainability.
Scala uses this syntax and I find it to be quite nice:

  (0 to 100).map(someFunc(arg1, arg2, _, arg3))
as does Mathematica. v useful for pattern matching as the hook for the function.
That can be fun and is only one step away from "point-free style". Just leave out the inputs altogether and switch to direct function composition:

    let myFunc = step1 >> step2 >> step3 >> etc
Personally, I often find point-free to be a little too terse, but occasionally it's much clearer than "regular" code.

https://en.wikipedia.org/wiki/Tacit_programming

I don't know, from this I can't really see the memory cost or GC impact. And the parallel statement doesn't tell me anything of the threading model. I know that I see a lot of intermediate states being created which is not so good. If this code were shown to compile to the imperative equivalent though, I'd definitely think about learning F#.
(comment deleted)
It is a nice article, but one thing I don't like is that it picks Event Driven Architecture without spelling out the reasons _why_.

"We used Azure Storage Queues to keep the whole flow asynchronous and more resilient to failures and load fluctuation."

That's not enough for me. I often encounter people building complex systems message queuing, despite failures being rare, retries being cheap and fast, and the hardware already being resilient to load.

In this article, a simple explanation of why each step actually _needs_ EDA/queues would be great - e.g. as the Product Matcher algorithm occasionally can't read a child's writing, an elf has to manually contact the child's parents to find out what they want so it can take a very long time.

> That's not enough for me. I often encounter people building complex systems message queuing, despite failures being rare, retries being cheap and fast, and the hardware already being resilient to load.

I'm a bit familiar with Azure Queues and Azure functions and I'll try to provide some more context here. But short answer: Using Azure Queues here actually makes things less complicated.

An Azure function (or any function) doesn't do much in isolation. Someone has to call it, so you've got to figure out: who's calling this function, when are they calling it, and what context do they have at call-time?

General purpose functions allow a wide range of possibilities to the above questions, and it's a lot easier to shoot yourself in the foot. So many people implement their own queue-like functionality that Azure decided to abstract it away into a general purpose model that roughly says: This function is called whenever there are unprocessed items remaining in the queue, and only information that's in the queue is passed to the function.

When you buy into that model, you get a whole bunch of stuff done for you: They've added common functionality like retry logic, expiry of queue items, security/permissions/tokens and https endpoints, logging that provides visibility into failures.

It's eliminated a lot of boilerplate code and it's also an architectural model that's easily understood. I'm a big fan myself.

[also, disclaimer, I'm an MS employee, but I don't work for Azure]

Hi, the author here. It's a good point and a valid concern, but I did an explicit choice here: I didn't want to make the article longer. I have a prequel post which describes my thinking of why durable functions make sense, including a primer for the event-driven approach: https://hackernoon.com/making-sense-of-azure-durable-functio...
Cool - so that article you link sets the context for the second.

I do think it does HTTP a disservice though - when you switch from HTTP to EDA you ditch the well known Uniform Interface and a lot of the benefits you get for free,(i.e. robust caching/proxying/routing, all the things a Service Mesh can do).

Instead of ditching HTTP, it would be nice to see people building async HTTP APIs, where the API responds with 201 Accepted (probably using a message queue behind the scenes) rather than the brittle systems you describe, where everything comes back under a 200.

If I may ask, which language is more functional feature wise, F# or OCaml?
"More functional" is not a precise term. However OCaml would fit most everyone's idea of "functional" more than F#. F# has to be compatible with the underlying .Net runtime, so it cannot have features like abstraction over modules, which is a very powerful functional abstraction in OCaml. F# also builds mostly on libraries built in C#, so unless you want to build wrappers on top of everything, you'll be doing a lot of OOP in F# as well. (Which I think is fine, I think OOP fits well in lots of cases and it's nice to be able to mix and match).