Consequence
Simple monad implementation with clear and consistent syntax.
Usage
A monad has a value that it is wrapped around. Its value can be anything: String, Module, Proc etc... It takes its value as its only argument and can be initialized using the element reference syntax:
require 'consequence'
include Consequence
my_monad = Monad[4]
Its value can be retrieved with the #value
getter method:
my_monad.value # 4
To create new monad types, simply inherit from Monad
:
class Foo < Monad; end
class Bar < Monad; end
A monad is equal to another monad only if both it's type and value are equal:
Foo[0] == Foo[1] # false
Foo[0] == Bar[0] # false
Foo[0] == Foo[0] # true
A monad respond to query methods for all defined monads:
Foo[0].foo? # true
Foo[0]. # false
Operations
Monads have two main operations >>
and <<
.
Both take an object with a #call
method, such as a proc, for their only argument. The #call
method can take 0-2 arguments and the monad will match them, even if it's a lamda or method object with arity checking.
It's first argument will be passed the monads value:
Foo[4] << ->(v) { puts v }
# $ 4
It's second argument will be passed the monad itself. This is useful for making decisions based on the monads type.
Foo[0] << ->(v, m) { puts v if m.foo? }
# $ 0
>>
The >>
operation may be variously thought of as the 'map', 'bind' or 'join' operations, but has been left unnamed to avoid any specific connotations.
It takes the result of the argument's #call
method and passes it on:
Foo[0] >> ->(v) { Bar[v] } # Bar[0]
If the returned result that is not a monad, it is wrapped up in one so the chain can continue:
Foo[0] >> ->(v) { v + 1 } # Foo[1]
<<
The <<
operation may be thought of as the 'pipe' operation.
It calls the argument's #call
method, but ignores the return value:
Foo[0] << ->(v) { v + 1 } # Foo[0]
This is useful for creating 'side effects', such as logging or assigning instance variables:
Foo[0] << ->(v) { @side_effect = v + 1 } # Foo[0]
puts @side_effect
# $ 1
Built-In Types
NullMonad
The NullMonad
is a monad whose >>
and <<
operations have been overriden to ignore all input:
NullMonad[0] >> ->(v) { v + 1 } # Consequence::NullMonad[0]
This is different from the <<
operation, because the argument's #call
method isn't even run, so can cause no side effects:
NullMonad[0] << ->(v) { @side_effect = v + 1 }
puts @side_effect.inspect
# $ nil
For both operations, the NullMonad
returns itself, so a chain of operations can be called on it without causing an error. This can be useful for stoping a chain midway through safely:
Continue = Class.new(Monad)
Stop = Class.new(NullMonad)
drop_first = ->(v) { v[1..-1] }
check_empty = ->(v, m) { v.empty? ? Stop[v] : m }
Continue[[1, 3, 4]] >> drop_first >> check_empty >> # Continue[[3, 4]]
drop_first >> check_empty >> # Continue[[4]]
drop_first >> check_empty >> # Stop[[]]
drop_first >> check_empty # Stop[[]]
Success & Failure
A Success
monad wraps up all exceptions in a Failure
monad:
Success[0] >> ->(v) { 5 / v }
# Consequence::Failure[#<ZeroDivisionError: divided by 0>]
A Failure
monad is a subclass of the NullMonad
so all successive chained operations are ignored.
For an example, check out the Success & Failure example on the wiki.
Something & Nothing
A Something
monad wraps up a nil result in a Nothing
monad:
Something[[1, 3, 5]] >> ->(v) { v[4] } # Consequence::Nothing[nil]
A Nothing
monad is also a subclass of the NullMonad
so all successive chained operations are ignored. This prevents MissingMethod
errors from trying to call a method on a nil
.
A Nothing
responds positively to the #nil?
method:
Nothing[nil].nil? # true
Eventually
The Eventually
monad hooks up the callbacks of a chain of methods that may be executed some time in the future. This is useful for dealing with asynochronous calls.
For an example, check out the Eventually example on the wiki.
DelegatesToValue
DelegatesToValue
is a module that can be included into Monad that adds a #method_missing
method to delegate calls to its value:
Foo.include DelegatesToValue
Foo[[1, 4, 6]].map {|n| n + 1} # Foo[[2, 5, 7]]
It delegates via the >>
operation, so subclasses of the NullMonad will respond to delegated method calls, but still take no action:
dangrous_hash = {user: {orders: {1 => {price: 3.99} } } }
Something[dangrous_hash][:user][:orders][1][:price] # Consequence::Something[3.99]
Something[dangrous_hash][:user][:orders][2][:price] # Consequence::Nothing[nil]
#to_proc
Before either operation calls the argument's #call
method, the #to_proc
method is called on it if it has one. This can be useful if the operation is given an object that has no #call
method, but has a #to_proc
.
A good example of this is the Symbol object, whose #to_proc
method supplies a proc that sends the symbol as a message to its first argument. In this case this means calling that method on the value:
Foo[[1, 4, 7]] >> :pop # Foo[7]
Wiki
To find some examples and information about utils, check out the wiki and feel free to contribute to it.
Inspirations
Installation
Add this line to your application's Gemfile:
gem 'consequence'
Contributing
- Fork it ( https://github.com/[my-github-username]/consequence/fork )
- Create your feature branch (
git checkout -b my-new-feature
) - Commit your changes (
git commit -am 'Add some feature'
) - Push to the branch (
git push origin my-new-feature
) - Create a new Pull Request