Live data from Hacker News

Monads in C# (Part 2): Result

alexyorke.github.io

21–30 of 75 posts

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

#21
I've had the misfortune of working on a C# code base that uses this pattern for many years.

I've also used it with F#, where it feels natural - because the language supports discriminated unions and has operators for binding, mapping etc. Without that, it feels like swimming against the tide.

Code has a greater cognitive overhead when reading it for the first time. And there is always a big over head for new starters needing to understand the code.

It feels idiomatic in F#. It feels crow-barred in with C#

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

#22

Small OT but part of me dies when data types that respect some laws are just labeled monads. Nobody calls an array a monad, even though an array admits a monad instance. Option, Result, Array, Either, FunkyFoo, whatever you want are just data types. They only become monads when combined with some functions (map, bind, apply, flatmap), and that combination of things respects a set of law. But calling a data type alone…

I was wondering about that. "Monad" is a mildly obfuscatory term for "function that takes one argument and returns one value of the same type", and a List is not a function.

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

#23
post #16

Earlier quoted context omitted.

This code path leaves entire classes of unhandled parse exceptions on the table. I have a hard time believing this could be intentional. The safest and most concise approach is to use int.TryParse on the inputId and throw if it returns false.

Does it? Int.Parse says it can only return those 2 exceptions or ArgumentNullException, but nulls have been handled already. https://learn.microsoft.com/en-us/dotnet/api/system.int32.pa...

Fine but with all that code we are implying there is some case we don't want to catch for some reason. If any effective parse error should always throw we should simply do that instead of playing games.

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

#24

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…

Doing it like this (or preferably with a custom exception) translates the technical problem into a domain problem. Without doing this, callers can't properly handle it. FormatException or OverflowException could be thrown at multiple locations, not just in parsing the user ID. This here is an InvalidUserIdException. It could be derived from ArgumentException, but IMHO InvalidOperationException is not appropriate.

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

#25
post #24

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…

Doing it like this (or preferably with a custom exception) translates the technical problem into a domain problem. Without doing this, callers can't properly handle it. FormatException or OverflowException could be thrown at multiple locations, not just in parsing the user ID. This here is an InvalidUserIdException. It could be derived from ArgumentException, but IMHO InvalidOperationException is not appropriate.

Translating into a custom exception is the way to go here. Bubbling up exceptions from your abstractions is fine for development but not a good experience for users of your API.

I would rather see custom exceptions thrown than rethrowing.

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

#26

I've had the misfortune of working on a C# code base that uses this pattern for many years. I've also used it with F#, where it feels natural - because the language supports discriminated unions and has operators for binding, mapping etc. Without that, it feels like swimming against the tide. Code has a greater cognitive overhead when reading it for the first time. And there is always a big over head for new starters…

I felt the same way with fp-ts then effect in typescript. Pretty cool libraries and I learned a lot about FP while trying them out for a couple of years, but a lot of ceremony and noise due to them (especially effect) almost being a new language on top of typescript.

Recently got the opportunity to try out elixir at my job and I'm liking it thus far, although it is an adjustment. That static typing and type inference are being added to the language right now is helpful.

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

#27
This is all very basic, instead you can use C#'s new static interface methods feature to create higher-kinded traits where you can properly generalise over a monad trait (or applicatives, functors, foldables, etc.), which is what I do in language-ext [0].

I'm not saying that implementing SelectMany for specific data-types isn't valuable. It certainly ends up with more elegant and maintainable code, but the true power of monads and other pure-FP patterns opens up when you can fully generalise.

* I have a blog series on it that covers implementing Semigroups, Monoids, Functors, Foldables, Traversables, Applicatives, Monads, and Monad Transformers (in C#) [1]

* The monad episode (my 'Yet Another Monad Tutorial') [2]

* An entire app generalised over any monad where the monad must support specific traits [3]. It's the program I use to send out the newsletters from my blog.

Happy to answer any questions on it.

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

[1] https://paullouth.com/higher-kinds-in-c-with-language-ext/

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

[3] https://github.com/louthy/language-ext/tree/main/Samples/New...

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

#28
post #22

Small OT but part of me dies when data types that respect some laws are just labeled monads. Nobody calls an array a monad, even though an array admits a monad instance. Option, Result, Array, Either, FunkyFoo, whatever you want are just data types. They only become monads when combined with some functions (map, bind, apply, flatmap), and that combination of things respects a set of law. But calling a data type alone…

I was wondering about that. "Monad" is a mildly obfuscatory term for "function that takes one argument and returns one value of the same type", and a List is not a function.

Where did you get that definition?

"function that takes one argument and returns one value of the same type" is the identity function.

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

#29

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…

The original exception is available[1] in the InnerException though, so upstream can handle those differently. [1]: https://learn.microsoft.com/en-us/dotnet/api/system.invalido...

That works somewhat for 2 levels of exceptions. When you start having dig into several levels of inner exceptions, gods help you.

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

#30
post #5

Idk, to me that constant Result looks extremely ugly and unergonomic, even just to type. I understand that this is complicated topic and there were a lot of strong opinions even inside of Google about it, but god, I miss absl::StatusOr and ASSIGN_OR_RETURN. Yes, it won’t work without preprocessor magic (and that’s why this article goes through heavy functional stuff, otherwise it just cannot work in language like C#)…

Is this similar? https://github.com/amantinband/error-or

What's some of the "preprocessor magic" that makes this[1] more ergonomic to use?

[1]: https://github.com/abseil/abseil-cpp/blob/master/absl/status...

Post reply on HN