A Fairy Tale of F# and Durable Functions
hackernoon.com
A Fairy Tale of F# and Durable Functions
1–10 of 32 posts
Re: A Fairy Tale of F# and Durable Functions
#2F# 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
#3Nice 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!…
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"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
#5Nice 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!…
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
#6Nice 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 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
#7Nice 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…
(|>) :: a -> (a -> b) -> b x |> f = apply x f
Re: A Fairy Tale of F# and Durable Functions
#8Earlier 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
(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
#9It 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…
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
#10Earlier 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…
f >>> g = g . f
Works for any instance of Category.