Live data from Hacker News

A Fairy Tale of F# and Durable Functions

hackernoon.com

1–10 of 32 posts

Re: A Fairy Tale of F# and Durable Functions

#2
Nice article.

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

   // WishList -> Async
   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!

Re: A Fairy Tale of F# and Durable Functions

#3
post #2

Nice article. F# can be so readable, e.g.: // WishList -> Async 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!…

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.

Re: A Fairy Tale of F# and Durable Functions

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

Re: A Fairy Tale of F# and Durable Functions

#5
post #2

Nice article. F# can be so readable, e.g.: // WishList -> Async 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!…

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.

Re: A Fairy Tale of F# and Durable Functions

#6
post #2

Nice article. F# can be so readable, e.g.: // WishList -> Async 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!…

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 s…

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.

Re: A Fairy Tale of F# and Durable Functions

#7
post #2

Nice article. F# can be so readable, e.g.: // WishList -> Async 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!…

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 s…

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

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

Re: A Fairy Tale of F# and Durable Functions

#8
post #7

Earlier quoted context omitted.

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 s…

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.

Re: A Fairy Tale of F# and Durable Functions

#9
post #4

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…

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

Re: A Fairy Tale of F# and Durable Functions

#10
post #8
post #7

Earlier quoted context omitted.

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 m…

If the left to right order is preferred, Haskell has Control.Category.(>>>)

    f >>> g = g . f
Works for any instance of Category.
Post reply on HN