Most languages know better than to allow nilness to pass silently, just as they know better than to have asynchronism happen silently. The whole point of monads is to allow you to do this kind of thing in a low-overhead, but still explicit way.
The behavior described in the article didn’t seem very “monadic” to me either. Swallowing errors appears to be the opposite of what Monads are about - treating side effects explicitly. That said, I can definitely see the benefit of this behavior in certain cases, for example when it matters more to make something work than to make it correct.
Objective-C’s bottom propagation seems to work really well; perhaps it’s a cultural and not a technical distinction?
You’re absolutely right that implicit handling of nil can cause errors to be detected further from their causes–but the same is true of currying, which allows type errors to arise from widely separated parts of a program. Different languages have different habits around these kinds of convenience/safety tradeoffs.
FWIW, I find Clojure’s nil handling generally more convenient than a hindrance–functions which define nil often serve as their own base case in recursive algorithms, for example, which reduces the need for explicit branching. This is especially helpful when building up maps! And while I agree that Clojure’s default nil behaviors can make it easier to make mistakes, core.typed is quite effective at preventing NPEs when you want that degree of safety.
You’re absolutely right that implicit handling of nil can cause errors to be detected further from their causes–but the same is true of currying, which allows type errors to arise from widely separated parts of a program.
Are you equating runtime errors and type errors? I’ve not seen currying create confusing type errors. Confusing runtime errors, yes definitely, but if you have types - no.
Source-sink distances in GHC type errors are generally quite good, whereas I’ve seen things like Clojure vectors being functions create mind-bending source-sink distances in errors.
I think the way Swift handles this is a pretty happy medium. Values aren’t implicitly nullable, and nil propagation is explicit rather than implicit. This means that:
When you’re okay with nil propagating, you can very easily just chain optional values by doing something like:
let myValue : SomeType? = myObject?.doSomething()?.value as? SomeType
And myValue would be nil if myObject is nil. Useful in the cases when something like that is acceptable.
But if you absolutely need the value to not be nil, then you throw in an explicit optional unwrapping and you get crashes if it is nil:
let myValue : SomeType = myObject!.doSomething().value as! SomeType
And in this case myValue would either be non-nil or you’d get an exception.
Hi, Clojure expat -> Haskell here. Writing a book about Haskell with a linguist I taught to program too!
Wanted to pick a nit, apologies! Short-circuiting has nothing to do with monads, it’s a behaviour Hickey picked up from Ruby.
See Stephen Diehl’s excellent guide on “What I wish I knew when I was learning Haskell” for a quick outsider’s explanation:
It’s abstract structural manipulation that doesn’t mean much until you are talking about a specific instance. There is very little that you can say about monads that is true because of how simple and small they are! There’s not much surface area with which inexperienced programmers can use to create folklore.
Baked-in nil value short-circuiting is no more “monadic” than a Boeing 747 is a fighter jet. Said short-circuiting has caused me heart-wrenching errors in Clojure in the past as well.
OT: @bitemyapp Considering both the link you posted on your book page (to github) and your article from 31-12: What is - in your opinion - the current go-to source for learning Haskell (considering your book is not released yet ;-))?
I recommend what my guide says to do - do cis194 Spring ‘13 aka Yorgey’s course and then do the NICTA course.
Intentionally not recommending the newer cis194 courses. The material they covered got weird and stopped being appropriate for beginners. (I don’t know what happened)
Right now, at least until my book is out (fingers crossed hard), you have to piece your beginning Haskell bits together from a panoply of resources. The good news is, there are a lot of resources, but the approach to teaching the material is uneven/inconsistent. Not enough stuff with exercises.
Thanks! Will be keeping my eye out for your book (actually, for the email telling me to order it ;-)). Will start out with cis194 for now; have been postponing learning haskell for way too long now.
Thanks for the comments!
I thought that the bind implementation of maybe is to short-circuit, either doing the desired function or propagating the None. I feel like these “nil safe” functions are giving the same guarantee, only without having to call bind every time.
The behaviour of a single instance doesn’t define what Monads are, that’s the category error I’m describing here.
(>>=) :: Monad m => m a -> (a -> m b) -> m b
The type of >>= says absolutely nothing about short-circuiting. >>= isn’t really the maximally essential method for understanding Monad as it rolls in a weaker algebra called Functor. The method that is exclusive to Monad is join.
join :: Monad m => m (m a) -> m a
Monad has more to do with abstract structural reduction. >>= is a convenient default method to use when interacting with Monadic values because you have to map over the m structure (using Functor, mind, not Monad) anyway. You’re not mapping or binding over anything in Clojure.
Maybe no more defines Monad than Identity does. It’s a common mistake to make, but it needs squashed. There’s no Monad in Clojure as it is designed nor in how it’s typically used.
Throwing around the word Monad like this creates the bad intuitions and confusion that people complain about. This problem is culturally related to people misusing the word isomorphic to talk about using the same language on the client & server-side of an application when there’s no isomorphism.
That there is very little we can say about Monad that is true is precisely what makes it powerful. Maybe doesn’t make Monad useful, Monad makes Monad useful. This then brings us to parametricity and laws but I’ll save that for another day.
You can’t really do anything interesting until you have a type system that lets you talk about type constructors apart from their arguments (higher kinded types), which is what you lets you talk about the manipulation of structure independently of what it is structuring. For me, this is table-stakes for abstraction in FP, not a wish-list item. On my wish-list is refinement types, pleasant-to-use dependent typing, and the like for Haskell.
The Ruby/Clojure approach is about elision - ignoring details. Haskell makes them explicit, but then gives you the tools for abstracting over those details so you’re still writing code just-as-or-more expressive than dyn-langs.
that’s the category error
Intentional pun? I laughed regardless :)
Initially serious, realized what it was after I typed it, then had a nice, Yes, good comment. Send. moment.
Yes, good comment. Send.
The multimethods explanations don’t really add much over single callee. I think something more akin to the standard “asteriods” example would be much better: