7 ms·
Scala's Option monad versus null-conditional operator in C#
- joaoqalves 11y agotl; dr: Scala Option monad is more elegant :)
- VoiceOfWisdom 11y agoThe same can be accomplished in F# for those who want this power in the .net ecosystem type Address = { street : string; city : string option } type Author = { name : string; email : string; address : Address option } type Article = { title : string; content : string; author : Author option } let printCity (article : Article) = article.author |> Option.bind(fun author -> author.address) |> Option.bind(fun address -> address.city) |> Option.iter(fun city -> printfn "%s" city)
- daxfohl 11y agoBetter to do with the maybe workflow from Fsharpx: maybe { let! address = author.address let! city = address.city printfn %s city }
- louthy 11y agoC# has native support for monads (LINQ), so it's trivial to implement an Option monad. Or you can go the whole hog like I have my language-ext project [1][2]. In C# it would look like this: var city = from author in article.author from address in author.address from city in address.city select city.ToLower(); Which is arguably even more elegant than the Scala 'for' notation. As an aside, there was a discussion [3] on one of the language-ext issues about my Try<T> implementation, and I was asked to compare it to Scala's Try monad. The crux of it was that it's perfectly possible to capture the majority of the use-cases, but may be of interest in the context of this discussion. [1] https://github.com/louthy/language-ext https://github.com/louthy/language-ext [2] https://github.com/louthy/language-ext/blob/master/LanguageExt.Core/Option.cs https://github.com/louthy/language-ext/blob/master/LanguageE... [3] https://github.com/louthy/language-ext/issues/33#issuecomment-146557934 https://github.com/louthy/language-ext/issues/33#issuecommen...
- airless_bar 11y agoImplementing a monad is only 30% of the deal. Being able to abstract over monads (and other type constructors) ... that's the part that really matters. C# is not expressive enough for that.
- louthy 11y agoIt doesn't do higher-kinded polymorphism, no. If you're referring to something else, please elaborate on what it can't express. But for the example given in the blog piece it's exactly as expressive and concise - so it's not giving C# a fair hearing. It is actually possible to get most of the way to HKT using extension methods. But it involves a hell of a lot of typing. In my project I used a template generator to do it. It's still pretty limited though, and there isn't a single type-class called Monad.
- airless_bar 11y ago> It doesn't do higher-kinded polymorphism, no. That's what I referred to. > ... The point is that you can't leverage all the existing monad/functor/applicative/free/yoneda... libraries out there–they don't exist, because they can't be written in a way that would allow re-use.
- louthy 11y ago> all the existing monad/functor/applicative/free/yoneda... libraries out there–they don't exist Agreed. I can only hope one day that the CLR team decide it's a reasonable thing to support so that C# (and F#) can both support it. It seems to be the blockage to the language teams (they won't move without it, even if they could implement it in a weaker/slower way on top of the existing type system). I've jumped through many hoops to get as much of a functional 'base class library' as possible in C# (I needed it for a very large C# project I maintain and want to bring more stability to), but language support would be preferred.
- daxfohl 11y agoJust out of curiosity what kind of reusable monad libraries are out there for Scala/Haskell? Given the scope of everything between Maybe and IO, I've never understood what one could make that would be useful to "monads" in general (beyond the sugar for do-notation).
- CuriousSkeptic 11y agoThe null-conditional operator is a language feature. The Option monad is a class in the standard library. You can have Option in C# and use it with linq to get more or less the same feature and syntax. There are a few implementations on nuget if you don't want to write your own.
- jameslk 11y agoWhy not just use less dots to begin with? Or, in other words, apply the Law of Demeter[0]. To me, diving so many levels deep into dependencies is a code smell because it tightly couples code to sub-dependencies and makes refactoring more difficult. 0. https://en.wikipedia.org/wiki/Law_of_Demeter https://en.wikipedia.org/wiki/Law_of_Demeter
- daxfohl 11y agoThis is important for typical OO chaining i.e. you should avoid: serviceX.getServiceY().getServiceZ().getSomeValue(); But for just structured data it's not constructive to write accessor wrappers at each level. And for things like recursive data structures, it's not even possible.
- oofabz 11y agoMore about the Law of Demeter at Ward's Wiki: http://c2.com/cgi/wiki?LawOfDemeter http://c2.com/cgi/wiki?LawOfDemeter
- lmm 11y agoMonads are very useful for what OO people sometimes call "cross-cutting concerns" - something that's relevant at many levels but not the primary purpose of any one function. E.g. error handling, async, audit logs, database transaction boundaries.
- krcz 11y agoScala version can be written as one-liner as well; losing readability, but it might be ok for fast hacking / prototyping: article.author.flatMap(_.address).flatMap(_.city).map(_.toLowerCase).foreach(println) And in for-comprehension version there is no need to use yield, as we don't need result of println.
- dk8996 11y agoWe work with Scala at our company and this is the style we use. I think that it's something that even a new person to Scala can easy understand.
- Filligree 11y agoHell, I've never used Scala at all, and I can read that.
- closetnerd 11y agoYou can tell that "_.author" is a parameterless lambda?
- Filligree 11y agoUm, no? It takes one parameter. Wouldn't fit into the map type signature otherwise.
- psuter 11y agoAfter 10 years of programming in Scala, I must admit I still don't know which style I prefer.
- gmantom 11y agoWhy not just use F#, it's better than Scala and C# at these sorts of things and functional first and immutable first.
- runT1ME 11y agoIt's really not. I worked for a .NET shop that migrated some new projects to Scala. We evaluated F#, but decided the power of Scala was worth the platform switch. F# lacks higher kinds and implicit parameters, meaning you can't really use the type class pattern. This hurt our productivity.
- louthy 11y ago> lacks higher kinds It can be done in F# [1], but it's not pretty, and absolutely nails the compiler. The fact that the compiler can be cheated into doing it makes it even more frustrating that there isn't some official support. [1] https://github.com/mausch/FsControl https://github.com/mausch/FsControl
- azth 11y ago> F# lacks higher kinds and implicit parameters, meaning you can't really use the type class pattern. This hurt our productivity. Genuinely curious, but what are you doing that the lack of type classes hurts productivity so much?
- runT1ME 11y agoIt's just annoying to have to re-implement sequence/traverse and every combination of monad stacks you want to for each type. It's doable, but bug prone and frustrating. I now work for one of the largest scala teams in the US. We make heavy use of the Scalaz project and regularly use monad transormers or free monads and both require higher kinded types.
- deleted 11y ago[deleted]
- hibikir 11y agoAs usual when someone tries to get into explaining monads, mistakes happen. First, on the scala standard library and monads: Scala very carefully avoided creating a Monad trait, on purpose. So while List and Options are monads, we can't just go and say that everything that has a flatmap is a monad. Also, the reason we can combine options and lists in a for comprehension is not really that they both have a flatmap: Try doing that with Future instead, and it will not work quite so easily! The reason those two have such easy interoperability is that in Scala's Predef there is a conversion that turns an Option into a Traversable, which is being called under the covers. For comprehensions are very powerful, but switching from one monadic context to another without help isn't one of them. In the case of Future and List, for instance, we'd have to use some of the utility methods in the Future object that can help transform a future into a List[Future], and a List[Future] into Future[List] Don't get me wrong, it's great to see people trying to explain this kind of stuff, but it is so very hard to cover the topic in a way that is both approachable and that doesn't leave people with intuitions that will fail them outside of the given example.
- miloszpp 11y ago[Author] Thanks for your remarks. I agree on the first point. As to the second, I didn't mean that having flatmap is the reason why we can combine Options and Lists. I meant that Options are composable with other Options and Lists are composable with other Lists. I understand the problem of working with different monads. I looked into monad transformers at some point and found them difficult to work with so my approach now is to avoid mixing monad types (for example instead of having Future[Option[T]], just use a failed Future instead of None).
- jeroen 11y agoConsole.WriteLine(article?.Author?.Address?.City?.ToUpper()); That is actually a bit different than the first piece of code, since it calls Console.WriteLine.
- draw_down 11y agoI think this is the first time I've ever really understood what the hell flatMap is for. So, thanks!
- miloszpp 11y ago[Author] That's awesome to hear that, thanks :)
- qwtel 11y agoIn case anybody is interested, this is how you would do this in clojure: (println (some-> article :author :address :city upper-case))
- finishingmove 11y agoThanks, I'm learning Clojure and things like this make me all warm and fuzzy inside :)
- ubercow 11y agoI didn't know about some->, that would have been handy recently. Cheers.
- sandisk5 11y ago(some-> article :author :address :city upper-case println) So that you don't print nil if some-> returns nil.
- wantreprenr007 11y agoMuch better than the usual terrible patterns of pyramids of doom. Another reason to set tab stops to 4+ characters: make PoDs impractical and better code legible.
- paulddraper 11y agoThe differences go far beyond syntax. Option/Maybe can represent arbitrary optionality, while null is limited to a sentinel value. I wrote a blog post about null (https://www.lucidchart.com/techblog/2015/08/31/the-worst-mistake-of-computer-science/ https://www.lucidchart.com/techblog/2015/08/31/the-worst-mis...) and this one of the examples: Suppose you have a generic key-value store. If a value is not present, the lookup returns null. You use it for a cache that maps user IDs to phone numbers. Some people don't have phone numbers, i.e. their phone number is null. But now you can't represent that in your cache. (JS "fixes" this -- if you want to call it that -- by using a second type of null: undefined.) Option is generic and can applied arbitrarily deep; null/Nullable is a sentinel that can be used once. Option is arbitrarily composable, and the superior choice.
- merb 11y agoActually in scala for just getting the city and lowercase it I would write that: article.author.flatMap(_.address.flatMap(_.city.map(_.toLowerCase))) I know that's by far not as good looking as the c# version, however at the end all comes down to syntactic sugar.