6 ms·
Nice article. F# can be so readable, e.g.: // WishList -> Async<Reservation> let workflow (wishlist: WishList) = async { // 1. Find matches for e
by LandR 8y ago
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!
- bunderbunder 8y agoWhat 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.
- dvlsg 8y agoI 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.
- tombert 8y agoI 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.
- bunderbunder 8y agoIt'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.
- tombert 8y agoWhile 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.
- 8y ago
- arwhatever 8y agoI 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.
- Tarean 8y agoWith some combinators you can indeed do this for any expressions, haskell has a tool that does this transformation automatically http://pointfree.io http://pointfree.io This can quickly become unreadable but used well it can hide some noise and meaningless variable names.
- jcmoscon 8y agoThis is why I love Haskell. You can easily define your own "|>" operator: (|>) :: a -> (a -> b) -> b x |> f = apply x f
- platz 8y agobut 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.
- sparkie 8y agoIf the left to right order is preferred, Haskell has Control.Category.(>>>) f >>> g = g . f Works for any instance of Category.
- hacker_9 8y agoI 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#.
- deleted 8y ago[deleted]