require "contracts" module Ribimaybe module Maybe include Contracts # Ensure constants are available to Ruby contracts. module Nothing; end class Just; end module Nothing include Contracts # Nothing string representation. def self.to_s "Nothing" end alias_method :inspect, :to_s # Compares a Nothing to another Maybe. # # ==== Attributes # # * +other+ - The other Maybe value. Contract Or[Nothing, Just] => Bool def self.===(other) self == other end # No operation. Always returns the default value. Contract Any, Proc => Any def self.maybe(default, &_) default end # No operation. Always returns Nothing. Contract Proc => Nothing def self.map(&_) self end # No operation. Always returns Nothing. Contract Any => Nothing def self.apply(_) self end class << self alias_method :>>, :apply end # No operation. Always returns Nothing. Contract Proc => Nothing def self.bind(fn = nil, &_) self end class << self alias_method :>=, :bind end end class Just include Contracts def initialize(value) @value = value end # Just string representation. def to_s "Just(#{@value.inspect})" end alias_method :inspect, :to_s # Compares a Just to another Maybe. # # ==== Attributes # # * +other+ - The other Maybe value. # # ==== Examples # # Just(1) == Just(1) # => true # Just(1) == Just(2) # => false # Just(1) == Nothing # => false Contract Or[Nothing, Just] => Bool def ==(other) other.maybe(false) do |value| @value == value end end # Fetches a value out of a Just and returns the application # of fn to the internal value. # # ==== Attributes # # * +_+ - Default value that's never used. # * +fn+ - Function to be applied to value inside Just. # # ==== Examples # # Just(1).maybe(false) { |x| x == 1 } # => true # Just(1).maybe(42) { |x| x } # => 1 Contract Any, Proc => Any def maybe(_, &fn) fn.curry.(@value) end # Applies fn to a value inside Just and re-wraps it in another Just. # # ==== Attributes # # * +fn+ - Function to be applied inside Just. # # ==== Examples # # Just(1).map { |x| x + 1 } # => Just(2) # Just { |x, y| x + y }.map { |f| f.(1) } # => Just(#) Contract Proc => Just def map(&fn) Just.new(fn.curry.(@value)) end # Applies fn inside Just to a value in a Just and re-wraps the result in # a Just. Note that functions are curried by default. # # ==== Attributes # # * +value+ - Maybe value. # # ==== Examples # # Just do |x| # x + x # end.apply(Just(1)) # => Just(2) Contract Or[Nothing, Just] => Or[Nothing, Just] def apply(value) value.map { |v| @value.curry.(v) } end alias_method :>>, :apply # Applies fn to value inside Just (note contract constraint). # # ==== Attributes # # * +fn+ - Function to be applied inside our Just. # # ==== Examples # # Just(1).bind do |x| # unit(x + x) # end # => Just(2) Contract Proc => Or[Nothing, Just] def bind(fn = nil, &block) (fn || block).curry.(@value) end alias_method :>=, :bind end # Converts nil to Nothing or lifts value into a Just. Accepts a optional # block or value. # # ==== Attributes # # * +value+ - Value to be wrapped. # * +fn+ - Block or function to be wrapped. # # ==== Examples # # Maybe(nil) # => Nothing # Maybe(1) # => Just(1) # Maybe { |x| x } # => Just(#) Contract Any, Or[nil, Proc] => Or[Nothing, Just] def Maybe(value = nil, &fn) (value || fn) ? Just.new(value || fn.curry) : Nothing end alias_method :Just, :Maybe alias_method :pure, :Maybe alias_method :unit, :Maybe end end