Live data from Hacker News

Monads in C# (Part 2): Result

alexyorke.github.io

51–60 of 75 posts

Re: Monads in C# (Part 2): Result

#51
post #50
post #46

Result result = ParseId(inputId) .Bind(FindUser) .Bind(DeactivateDecision); This does not implement monads as Haskell has them. In particular, Haskell can do: do id Note id getting used multiple times. "Monad" is not a pipeline where each value can be used only once. In fact if anything quite the opposite, their power comes from being able to use things more than once. If you desugar the do syntax, you end up with a…

In C# you can implement SelectMany for a type and that gives this: from id in ParseId(inputId) from user in FindUser(id) from posts in FindPostsByUserId(id) from res in DeactivateDecision(user, posts) select res; It is the equivalent to do-notation (was directly inspired by it). Here's an example from the language-ext Samples [1], it's a game of 21/pontoon. > I wrote what you might call an acid test for monad impleme…

It looks like it. My claim was not (and is not) that C# can't implement it, but that what is discussed in the post does not.

Re: Monads in C# (Part 2): Result

#52
post #51
post #50

Earlier quoted context omitted.

In C# you can implement SelectMany for a type and that gives this: from id in ParseId(inputId) from user in FindUser(id) from posts in FindPostsByUserId(id) from res in DeactivateDecision(user, posts) select res; It is the equivalent to do-notation (was directly inspired by it). Here's an example from the language-ext Samples [1], it's a game of 21/pontoon. > I wrote what you might call an acid test for monad impleme…

It looks like it. My claim was not (and is not) that C# can't implement it, but that what is discussed in the post does not.

Fair enough :)

By the way, I happen to agree on the general point, in my blog teaching Monads in C# [1], I wrote this:

"I often see other language ecosystems trying to bring monads into their domain. But, without first-class support for monads (like do notation in Haskell or LINQ in C#), they are (in my humble opinion) too hard to use. LINQ is the killer feature that allows C# to be one of very few languages that can facilitate genuine pure functional programming."

So, yeah, regular fluent method chaining isn't really enough to make monads useful.

[1] https://paullouth.com/higher-kinds-in-csharp-with-language-e...

Re: Monads in C# (Part 2): Result

#53
I'm glad Paul Louth of https://github.com/louthy/language-ext/ is here in the comments.

At this point basically everyone has been exposed to the concept of `Option/Result/Either/etc.`, and discussions typically end up revolving around the aesthetics of exception throwing vs. method chaining vs. if statements etc. without any concept of the bigger picture.

LanguageExt really presents a unified vision for and experience of Functional Programming in C# for those are who truly interested, akin to what's been going on in the Scala ecosystem for years.

I've been using it and following its development for a few years now and it continually impresses me and makes C# fresh and exciting each day.

Re: Monads in C# (Part 2): Result

#55

I really dislike this pattern: try { id = int.Parse(inputId); } catch (Exception ex) when (ex is FormatException or OverflowException) { throw new InvalidOperationException("DeactivateUser failed at: parse id", ex); } Where all you're doing when you catch an exception is throwing it in a more generic way. You could just let the FormatException or OverflowException bubble up, so the parent can handle those differently…

That's not how you're supposed to handle this kind of errors (according to .NET designers) - there's 2 kinds of errors in concept, everyday errors that are to be expected, such as a dictionary not containing a key, or in this case, a user supplying a badly formatted integer. For this you have the Try.. methods with TryGetValue, TryParse etc.

Go for example, allows for multiple return values, so it allows more elegant handling of this exact cass.

Then there's the serious kind of error, when something you didn't expect goes wrong. That's what exceptions are for. If this distinction is followed, then you don't want to handle specific exceptions (with very few notable distinctions, like TaskCanceledException), you just either pick a recoverable function scope (like a HTTP handler), and let the exception bubble to its top, at which point you report an error to the user, and log what happened.

If such a thing is not possible, just let the program crash.

Re: Monads in C# (Part 2): Result

#57
It has been a long-standing trend/belief/whatever that FP is just somehow better, it's kind of have been this belief that has endured for decades. Part of that belief is that exceptions are bad and option/result types are the way to go for proper error handling.

I don't think this is true at all, they are just different, with procedural programming being control-flow oriented and fp being dataflow oriented.

Monads are just dataflow oriented error handling, which is comes with its own set of tradeoffs and advantages, the key disadvantages being the necessity of an advanced type inference-system, to allow natural looking usage, and the function signatures having to support the notion that this function can indeed throw an error.

Implementation wise, the generated assembly is not more efficient, as the error passing plumbing needs to appear at every functions return site, even if no error happens.

I'm not saying Monads as error handling are an inherently bad concept, but neither are exceptions (as many usually suggest), and using both depend heavily on language support to make them ergonomic, which in the case of C# and monads, is missing.

Re: Monads in C# (Part 2): Result

#58

I'm glad Paul Louth of https://github.com/louthy/language-ext/ is here in the comments. At this point basically everyone has been exposed to the concept of `Option/Result/Either/etc.`, and discussions typically end up revolving around the aesthetics of exception throwing vs. method chaining vs. if statements etc. without any concept of the bigger picture. LanguageExt really presents a unified vision for and experienc…

Aww, thanks Mike! And thank you for the contributions and suggestions too :)

> At this point basically everyone has been exposed to the concept of `Option/Result/Either/etc. and discussions typically end up revolving around the aesthetics of exception throwing vs. method chaining vs. if statements etc. without any concept of the bigger picture.

I think this is a really important point. 12 years ago I created a project called 'csharp-monad' [1], it was the forerunner to language-ext [2], which I still keep on github for posterity. It has the following monadic types:

    Either
    IO
    Option
    Parser
    Reader
    RWS
    State
    Try
    Writer
One thing I realised after developing these monadic types was that they're not much use on their own. If your List type's Find method doesn't return Option, then you haven't gained anything.

I see others on here are taking a similar journey to the one I took over a decade ago. There's an obsession over creating Result types (Either and Fin in language-ext, btw) and the other basic monads, but there's no thought as to what comes next. Everyone of them will realise that their result-type is useless if nothing returns it.

If you're serious about creating declarative code, then you need an ecosystem that is declarative. And that's why I decided that a project called "csharp-monad" was too limiting, so I started again (language-ext) and I started writing immutable collections, concurrency primitives, parsers, and effect systems (amongst others). Where everything works with everything else. A fully integrated functional ecosytem.

The idea is to make something that initially augments the BCL and then replaces/wraps it out of existence. I want to build a complete C# functional framework ecosystem (which admittedly is quite an undertaking for one person).

I'm sometimes a little wary about going all in on the evangelism here. C# devs in general tend to 'stick to what they know' and don't always like the new, especially when it's not idiomatic - you can see it in a number of the sub-threads here. But I made a decision early on to fuck the norms and focus on making something good on its own terms.

And for those that wonder "Why C#?" or "Why not F#?", well C# has one of the best compilers and tooling ecosystems out there, it's got an amazing set of functional language features, it will have ADTs in the next version, and it has a strong library ecosystem. It also has the same kind of borrow checker low level capability as Rust [3]. So as an all-rounder language it's quite hard to beat: from 'to the metal bit-wrangling', right the way up to monad comprehensions. It should be taken more seriously as a functional language, but just generally as a language that can survive the turmoil of a long-lived project (where mostly you want easy to maintain code for the long-term, but occasionally you might need to go in and optimise the hell out of something).

My approach will piss some people off, but my aim is for it to be like the Cats or Scalaz community within the larger Scala community.

It's certainly a labour of love right now. But, over a decade later I'm still enjoying it, so it can't be all bad.

(PS Mike, I have new highly optimised Foldable functionality coming that is faster than a regular C# for-loop over an array. Watch this space!)

[1] https://github.com/louthy/csharp-monad

[2] https://github.com/louthy/language-ext

[3] https://em-tg.github.io/csborrow/

Re: Monads in C# (Part 2): Result

#59

It has been a long-standing trend/belief/whatever that FP is just somehow better, it's kind of have been this belief that has endured for decades. Part of that belief is that exceptions are bad and option/result types are the way to go for proper error handling. I don't think this is true at all, they are just different, with procedural programming being control-flow oriented and fp being dataflow oriented. Monads ar…

On your definition of FP you're right. But pure functional programming has the following over regular imperative coding:

* Fewer bugs: Pure functions, which have no side effects and depend only on their input parameters, are easier to reason about and test, leading to fewer bugs in the code-base.

* Easier optimisation: Since pure functions do not have any side effects, they can be more easily optimised by the compiler or runtime system. This can lead to improved performance.

* Faster feature addition: The lack of side effects and mutable state in pure functional programming makes it easier to add new features without introducing unintended consequences. This can lead to faster development cycles.

* Improved code clarity: Pure functions are self-contained and independent, making the code more modular and easier to understand. This can improve code maintainability.

* Parallelisation: Pure functions can be easily parallelised, as they do not depend on shared mutable state, which can lead to improved scalability.

* Composition: This is the big one. Only pure functional programming has truly effective composition. Composition with impure components sums the impurities into a sea of undeclared complexity that is hard for the human brain to reason about. Whereas composing pure functions leads to new pure functions – it's pure all the way down, it's turtles all the way down. I find it so much easier to write code when I don't have to worry about what's going on inside every function I use.

That's obviously way beyond just having a declarative return type. And in languages like C# you have to be extremely self-disciplined to 'do the right thing'. But what I've found (after being a procedural dev for ~15 years, then a OO dev for ~15 years, and now an FP dev for about 12 years) is that pure functional programming is just easier on my brain. It makes sense in the way that a mathematical proof makes sense.

YMMV of course, but for me it was a revelation.

Re: Monads in C# (Part 2): Result

#60

Good review, but I frankly don't see the point of Result . Just have the error be an exception type as exceptions idiomatically represents errors in .NET. Then you're down to only 1 type argument which is much less noisy. That's what I've used for the result type in my library that I've been using for years. I don't use it often, but it's very handy when appropriate.

If you ask yourself what the meaning of the word 'exception' is and then consider how many failures are exceptional, then one quickly realises that exceptions are the worst thing you could use to represent expected failure conditions.

The only time we should throw (or even pass around) exceptions is if there isn't a slot in the co-domain to inject a value in to.

Post reply on HN