# optional ## option types to make nils a thing of the past [![Build Status](https://travis-ci.org/rsslldnphy/optional.png?branch=master)](https://travis-ci.org/rsslldnphy/optional) [![Code Climate](https://codeclimate.com/github/rsslldnphy/optional.png)](https://codeclimate.com/github/rsslldnphy/optional) Tony Hoare, inventor of the null reference, calls it his "billion-dollar mistake". You will be no stranger to the ubiquitous `NoMethodError: undefined method 'foo' for nil:NilClass`. But it doesn't have to be this way. There are, crucially, two distinct types of values (or rather lack of values) that are usually, in Ruby, represented as `nil`. There are values that should always be present - and here the fact that they are `nil` actually indicates that an error has occurred somewhere else in your program - and those values that *may or may not be set* - where each case is valid. For example, a person may or may not be wearing a hat. Using `nil` to represent these optional values leads to lots of ugly defensive coding - or worse, if you forget the ugly defensive coding in even a single place, to `nil`s leaking out into the rest of your program, causing it to blow up at some later point, from where it might be hard to track back to the original cause of the problem. `Option`s, a construct found in many functional languages, provide an alternative. They consist of, at the most basic level, a container that either contains a value (`Some` value) or does not (`None`). Because its a container, you can't access the value directly, so client code is forced to deal with both the case that it's there and that it isn't. It's a simple concept but leads to some surprisingly powerful uses. ## Creating Options Here's how you create an option that has a value (a `Some`), using some syntactic sugar provided by the [] method: Some[4] Some[Cat.new("Tabitha")] Some["Jelly"] Like `nil`, there's only one instance of `None`, so you don't need to create it. Here's how you use it: None ## Getting the value The simplest way to get the value of an `Option` is using the `value` method: Some[5].value # => 5 None.value # => raises ValueOfNoneError Why is it a good thing that calling `value` on `None` raises an error? Because you should only use `value` if you're sure that you should have a value at this point - if not having a value *is* an error. This means the code will blow up at the earliest possible opportunity, making it easier to track down the root cause of the problem. ## How do I know if I have a value? There are methods named as you'd expect to test for this: Some[5].some? # => true Some[5].none? # => false None.some? # => false None.none? # => true You can also pass a class or module constant to `some?`: Some["Perpugilliam Brown"].some? String # => true Some["Perpugilliam Brown"].some? Fixnum # => false This can come in handy when writing rspec tests: it { should be_none } it { should be_some Fixnum } But you shouldn't usually need to use these `some?` or `none?` methods. They basically add up to exactly the same ugly defensive code we're trying to avoid with `nil`s. Luckily `Option`s provide plenty of other, more elegant ways to access their values. ## Providing a default If not having a value is a valid case, you might want to provide a default in its place. You can do this using the `value_or` method: Some[5].value_or 0 # => 5 None.value_or 0 # => 0 You can also pass a block to `value_or` in case you don't want the default to be evaluated unless it is used: Some[5].value_or { puts "This won't be printed." ; 0 } # => 5 None.value_or { puts "This will be printed!" ; 0 } # => 0 ## Option is enumerable! `Option` supports all the same methods that enumerable supports - although it will return `Option`s rather than `Array`s in most cases. This leads to some pretty cool stuff. Here's `each`: Some[5].each do |value| puts "The value is #{value}!" end # => prints "The value is 5!" None.each do |value| puts "The value is #{value}!" end # => doesn't print anything (because there is no value) Or you can also use `for`: for value in Some[5] puts "The value is #{value}!" end # => prints "The value is 5!" for value in None puts "The value is #{value}!" end # => doesn't print anything And here's `map` (or `collect`): Some["caterpillar!"].map(&:upcase) # => Some["CATERPILLAR!"] None.map(&:upcase) # => None Options being enumerable comes in handy if you're using rails, too. Let's say you have a person model that has an `Option[Hat]`, and you want to render a `Hat` partial only if the person has one. You can simply use the `collection` key when rendering the partial - you don't even need an `if`: <%= render partial: 'hat', collection: @person.hat %> You can also use `select` and `reject` to assert things about the value: Some[5].select(&:odd?) # => Some[5] Some[5].reject(&:odd?) # => None And one last useful example, `flatten`: Some[Some[5], None, Some[7]].flatten # => Some[5,7] [Some[6], None, Some[999]].flatten # => [6,999] ## Pattern-matching You'll find `Option`s in many functional languages such as ML, Haskell (as the Maybe monad), Scala and F#. And in most cases, they will provide a way to access the values (not specific to `Option`s, but a more general part of the language), called pattern-matching. We don't have pattern-matching in Ruby, but `Optional` provides a basic version for use with `Option`s. Here's how to use it: option.match do |m| m.some { |value| puts "The value is #{value}" } m.none { puts "No value here" } end As you'd expect, the `some` branch is executed if there is a value, the `none` branch if there isn't. You can also add cases that assert based on the content of the option: option.match do |m| m.some(:fedora) { puts "This will be printed only if I'm passed a Some[:fedora]" } m.some(:trilby) { puts "This will be printed only if I'm passed a Some[:trilby]" } m.none { puts "This is printed if I'm passed a None" } end The first case that matches is the one the match clause evaluates to. You can also use lambdas as part of the match clauses like this: option.match do |m| m.some ->(x){ x.length > 2 } { puts "Printed if value's length is > 2" } m.some ->(x){ x.is_a? Array } { puts "Printed if value is an array (with lengt <= 2)" } m.none { puts "This is printed if I'm passed a None" } end ## Extra fun and goodness What's been described so far is what you'd usually expect from `Option`s in other languages. `Optional`, however, provides a few extra bits and pieces you may want to have a play with. ## Logical operators (sort of logical, anyway) Got two optional values and want to do something only if they *both* have values? Use `&`: Some[5] & Some[6] # => Some[5,6] Some[5] & None # => None None & Some[5] # => None None & None # => None Got two optional values, either of which might be `None`, and want to do something with one of them, doesn't matter which? Use `|`: Some[5] | Some[6] # => Some[5] Some[5] | None # => Some[5] None | Some[6] # => Some[6] None | None # => None NB. Technically, an `Option` should only have up to one value, but to allow the `&` operator and similar things `Optional` sort of cheats by treating 'multiple values' as a single value of type `Array`. ## Mapping through multiple functions You might find yourself needing to map an optional value through a number of functions. There's a handy way to do this with `Option`s: p = Person.create(name: "Russell!") Some[p].map_through(:name, :upcase) # => Some["RUSSELL!"] None.map_through(:name, :upcase) # => None ## Juxtaposing the result of multiple functions This one is nicked from Clojure (there might be other languages that have it, I don't know). Call a list of functions on an option value, returning a `Some` of their results: p = Person.create(name: "Russell!", age: 29, hat: :fedora) Some[p].juxt(:name, :age, :class) # => Some["Russell", 29, Person] None.juxt(:name, :age, :class) # => None If you're feeling adventurous you could always monkey patch this onto `Object` so it's available for everything. Might come in useful. ## That's about it I hope you find `Option`s useful. Let me know if you do: I'm @rsslldnphy on that Twitter. I'm very enthusiastic about optional types and functional programming techniques in general, so if you have a question or want advice on using options in your project I'll be happy to answer! If you'd like to contribute, create a pull request for the feature or fix you have in mind. That way we can discuss whether it's a good fit and how best to approach it before you start on the code. Contributions very welcome!