Rubyists can have a single monad, as a treat
I've been learning Go. The programming language. I've played a little of the board game, but never enough to get halfway decent at it. Both the language and the game are relatively simple and elegant, from what I can tell.
I don't have anything to say about Go yet. I think I really like it, but the thing I'm building isn't complex enough yet for me to be sure. I'll report back when I'm a little more confident.
I like normal code. I try to write normal code. Using monads in Ruby isn't a very normal thing to do, so I don't normally do it. There's one exception: the result monad. It's simply too useful.
Let's take a look at dry-monads. The gem provides two monads: Result and Maybe.
Call Me Maybe
Maybe is used to represent a value that may or may not be present. In normal Ruby, you'd use nil to represent an absence and any other value to represent the presence of that value. Say what you will about "The Billion Dollar Mistake", using nil is idiomatic Ruby.
To be clear, I love option types. I like what they get you in languages like OCaml or Rust. I like non-nullable types. I have no interest in bringing these patterns to Ruby.
# statements dreamed up by the utterly deranged
add_two = -> (x) { Maybe(x + 2) }
Maybe(5).bind(add_two).bind(add_two)
Mixed Results
The Result monad is used to represent the result of an operation. It can be either a Success or Failure and in either case can have any number of values associated with it.
In contrast to Maybe, Ruby has no idiomatic pattern for this. The gem ecosystem is full of different gems that offer different implementations of result objects, all of which vary in their APIs.
I don't even use 90% of the API of dry-monads' Result monad. In my codebases, I just want a consistent way to represent whether an option was successful or not, bundled with relevant values (errors, the actual output, et cetera). Result gives me that.
module MyAPI
include Dry::Monads[:result]
def get_thing(thing_id:)
value = do_api_request_or_whatever(thing_id)
Success(value)
rescue HTTPNotFoundError => e
Failure(:not_found)
rescue => e
Failure(e)
end
private
# ...
end
class MyControllerOrWhatever < ApplicationController
def show
result = MyApi.get_thing(thing_id: params[:thing_id])
case result
when Success
@thing = result.value!
render
when Failure(:not_found)
render("not_found", status: :not_found)
else
render("error", status: :unprocessable_content)
end
end
end
And yes, if the codebase doesn't have dry-monads yet and I can get away with just returning false in the error case, I'll do that. KISS and all that. But, as soon as I need to associate values with the success and fail modes, dry-monads is coming in.
At this point, you might be wondering: what is a monad? Well, me too. I've got no idea. I like this Result thing, though.
The Ruins of Beverast – Tempelschlaf
Every once in a while (often in the dead of winter) I slip into into a deep well of black metal. Usually starting with old school black metal, I slowly gravitate toward more atmospheric black metal by the end. There's always a pit stop at the discography of atmoblack/doom band The Ruins of Beverast.
While this winter hasn't driven me into such a depressive music spiral this year (mostly I'm just listening to the new Protomen album), I had to give Tempelschlaf a spin. This album is yet another fantastic entry in a long line of great albums.
Within its 59 minute runtime, you'll find gothic rock influences, psychedelic textures, and some great atmospheric synth usage alongside their typically black/doom template. The Ruins of Beverast managed to take the stylistic shift from their previous album, tighten it up, and channel it into something a more streamlined form.
It good. Have a listen.