TAlgebra
Installation
Add this line to your application's Gemfile:
gem 't_algebra'
And then execute:
$ bundle install
Or install it yourself as:
$ gem install t_algebra
Usage
t_algebra brings the basic categorical concepts of Functor, Applicative, and Monad into ruby, along with a handful of common instances.
Let's walk through TAlgebra::Monad::Maybe
, the monad representing optional results. Typically in ruby we have a method
which may return a value or nil should some underlying data be absent. For example user.city
may return nil
should the
user not have entered their city. Working with these possible nil
values can be a source of bugs and a source of code
complexity. The following code is illustrative:
# @return [String, nil]
def get_address(user)
street = user.street
return unless street
city = user.city
return unless city
state = user.state
return unless state
state = state.upcase
"#{street}, #{city} #{state}"
end
The class TAlgebra::Monad::Maybe
wraps a result which may or may not be nil, and uses the Monad tech of bind
+ fmap
to
sensibly manipulate that result. The below example uses chain/bound
notation (analagous to Haskell do/<-
notation or js async/await
)
to reproduce the same functionality:
# @return [TAlgebra::Monad::Maybe]
def get_address(user)
TAlgebra::Monad::Maybe.chain do
m_user = just(user)
street = bound { m_user.fetch(:street) }
city = bound { m_user.fetch(:city) }
state = bound { m_user.fetch(:state) }
"#{street}, #{city} #{state.upcase}"
end
end
This basic Maybe functionality extends neatly into the Either
monad, the monad representing results or errors, and
subsequently Parser
which can validate and map over complex data structure.
# @return [TAlgebra::Monad::Parser]
def get_address(user)
TAlgebra::Monad::Parser.chain do
# validate street is present with `#fetch!`
street = bound { fetch!(user, :street) }
# validate that the city is a string with is `#is_a?`
city = bound { fetch!(user, :city).is_a?(String) }
# validate that the state is a 2 letter long string with `#validate`
# and transform to all caps with `#fmap`
state = bound do
fetch!(user, :state)
.is_a?(String)
.validate("Is 2 letter code"){ |state| state.length == 2 }
.fmap(&:upcase)
end
"#{street}, #{city} #{state}"
end
end
Defining your own examples
Say we had a class ExtAPI
representing a an external api result or http error. (Similar to the Either monad.) We could implement this as a monad as
follows:
class ExtAPI
include TAlgebra::Monad
class << self
def call(verb, path)
result = ... make api cal
new(success: result)
rescue => e
new(http_error: {e.status, e.message})
end
#Implement Applicative's `.pure` interface
def pure(a)
new(success: a)
end
end
#Implement Monad's `#bind` interface
def bind
return new(http_error: http_error) if http_error
yield(success)
end
...
end
And it could be used like:
def user_cities
ExtAPI.chain do
users = bound { call('GET', '/users') }
profiles = bound { call('GET', "/users/profile?id=#{users.map(&:id).to_json}" }
profiles.map(&:city).uniq
end
end
Should either of the two calls fail, this method will return that error status and message to be handled by the client code. (Note that while the concept of Promise is monadic, the above ExtAPI implementation is entirely synchronous.)
Development
After checking out the repo, run bin/setup
to install dependencies. Then, run rake spec
to run the tests. You can also run bin/console
for an interactive prompt that will allow you to experiment.
To install this gem onto your local machine, run bundle exec rake install
. To release a new version, update the version number in version.rb
, and then run bundle exec rake release
, which will create a git tag for the version, push git commits and tags, and push the .gem
file to rubygems.org.
Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/t_algebra. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the code of conduct.
License
The gem is available as open source under the terms of the MIT License.
Code of Conduct
Everyone interacting in the TAlgebra project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.