Live data from Hacker News

Monads in C# (Part 2): Result

alexyorke.github.io

31–40 of 75 posts

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

#31

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…

I had a similar impression with using those constructs on TypeScript.

IMO it's hard to justify creating Option/Result wrappers when T|null and T|E will work well enough for the majority of use cases.

effect specifically feels like a different programming language altogether. And maybe going that path and compiling down to TS/JS could've been a better path for them. I'm not on the ecosystem though, so it's an uninformed thought.

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

#32
post #28
post #22

Earlier quoted context omitted.

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.

Identity function returns the same _value_.

If it's only the same _type_, but the value is not the same, then it's an endomorphism. The function definitions look the same `a -> a`.

string reversal, integer negation or toUpperCase are classical examples of endomorphisms.

Identity is a specific case of endomorphism.

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

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

You're right that domain specific exceptions would be much better.

As an aside, generating domain specific exceptions is precisely the kind of busywork that traditionally it is hard to find motivation to do but that LLMs excel at.

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

#34
post #28

Earlier quoted context omitted.

Where did you get that definition? "function that takes one argument and returns one value of the same type" is the identity function.

Identity function returns the same _value_. If it's only the same _type_, but the value is not the same, then it's an endomorphism. The function definitions look the same `a -> a`. string reversal, integer negation or toUpperCase are classical examples of endomorphisms. Identity is a specific case of endomorphism.

string reversal, integer negation or toUpperCase are classical examples of functions which will not compile as `a -> a`

The function which will compile as `a -> a` is the identity function.

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

#35
post #34

Earlier quoted context omitted.

Identity function returns the same _value_. If it's only the same _type_, but the value is not the same, then it's an endomorphism. The function definitions look the same `a -> a`. string reversal, integer negation or toUpperCase are classical examples of endomorphisms. Identity is a specific case of endomorphism.

string reversal, integer negation or toUpperCase are classical examples of functions which will not compile as `a -> a` The function which will compile as `a -> a` is the identity function.

That's correct, but I'm not sure how it relates to my comment as I said that `a -> a` is just an endomorphism.

identity, uppercase or negate are all endomorphisms, with identity being the only generic one.

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

#36
post #9

I've been playing around with some of the "standard/common monads in C# for a while now, in OSS ( https://github.com/pimbrouwers/Danom ) and at work. It's awesome. I can't imagine working without them anymore.

Do you have a comparison to other libraries like https://github.com/louthy/language-ext Yours looks a lot more idiomatic to C# (hence acceptable for a mixed code base) but the above linked more "systematic". Not that I have used any or have any competence.

I normally do a much deeper dive into the existing ecosystem before I write a library like this. In this case, I tried a few of the popular packages, but in the end decided my own pursuit of the exact types I wanted was more interesting. So, I just went for it.

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

#37
post #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…

Serious question, at this point, have all F# features been fully incorporated into C#?

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

#38

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…

Monadic binding and other functional mainstays in C# mostly fall into the same uncanny valley. Like non-exhaustive pattern matching, we get some nice sugar, but it’s not the same, and not a proper substitute for what we’re trying to do.

F# ~~ripped off~~ is deeply inspired by OCaml, with a very practical impact on its standard library: there are facilities available for all the functional programming jazz one hasn’t though about or bumped into. In active patterns, pattern matching, recursive list comprehensions, applicatives, or computation expressions when you bump into the corners of the language you find a deep, mature, OCaml core that nerds much smarter and more talented have refined for decades. The language was built around those facilities.

Bumping into the edges of the partial features in C# is a pretty common experience for me, resulting in choices about kludges to support a superficial syntax, raising concerns about goldbricking.

It feels crowbarred because it was.

“Railway oriented programming” passes over well as a concept, but it’s an easier sale when you see its use resulting in smaller, simpler, easier functions

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

#39
post #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…

Serious question, at this point, have all F# features been fully incorporated into C#?

Not discriminated unions, but they're coming (I think next version of C#). Although for now you can simulate them quite easily:

    public abstract record Either;
    public sealed record Left(L Value) : Either;
    public sealed record Right(R Value) : Either;
Pattern-matching works well with these simulated algebraic data-types. Obviously, exhaustiveness checks can't work on 'open' types, so it's not perfect, but you can unpack values, apply predicate clauses, etc.

Other more niche features like type-providers don't exist either (although arguably those could be done with source-generators in C#). It's been a long time since I did any F#, so not sure if there's anything new in there I'm unaware of.

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

#40
post #24

Earlier quoted context omitted.

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.

You're right that domain specific exceptions would be much better. As an aside, generating domain specific exceptions is precisely the kind of busywork that traditionally it is hard to find motivation to do but that LLMs excel at.

Re: Domain Specific Exceptions

Code snippets in IDEs like Visual Studio and refactoring tools like Resharper offer shortcut accessible auto generation of those kinds of entities with deterministic results. They also have routines to extract and move entities to their own files afterwards in a keyboard based workflow.

They are far less work than a prompt, faster, can be shared in the project or team, and are guarantee-able to confirm to coding standards, logging specifics and desired inheritance chain for your root domain exception. It works on-prem, offline, for zero tokens.

Post reply on HN