lib/rumonade/option.rb in rumonade-0.1.0 vs lib/rumonade/option.rb in rumonade-0.1.1
- old
+ new
@@ -1,57 +1,95 @@
require 'singleton'
require 'rumonade/monad'
module Rumonade # :nodoc:
- # TODO: Document this
+ # Represents optional values. Instances of Option are either an instance of Some or the object None.
+ #
+ # The most idiomatic way to use an Option instance is to treat it as a collection or monad
+ # and use map, flat_map, select, or each:
+ #
+ # name = Option(params[:name])
+ # upper = name.map(&:strip).select { |s| s.length != 0 }.map(&:upcase)
+ # puts upper.get_or_else("")
+ #
+ # Note that this is equivalent to
+ #
+ # # TODO: IMPLEMENT FOR COMPREHENSIONS
+ # # see http://stackoverflow.com/questions/1052476/can-someone-explain-scalas-yield
+ # val upper = for {
+ # name <- Option(params[:name])
+ # trimmed <- Some(name.strip)
+ # upper <- Some(trimmed.upcase) if trimmed.length != 0
+ # } yield upper
+ # puts upper.get_or_else("")
+ #
+ # Because of how for comprehension works, if None is returned from params#[], the entire expression results in None
+ # This allows for sophisticated chaining of Option values without having to check for the existence of a value.
+ #
+ # A less-idiomatic way to use Option values is via direct comparison:
+ #
+ # name_opt = params[:name]
+ # case name_opt
+ # when Some
+ # puts name_opt.get.strip.upcase
+ # when None
+ # puts "No name value"
+ # end
+ #
class Option
class << self
+ # Returns a new Option containing the given value
def unit(value)
Rumonade.Option(value)
end
+ # Returns the empty Option (None)
def empty
None
end
end
- def initialize
+ def initialize # :nodoc:
raise(TypeError, "class Option is abstract; cannot be instantiated") if self.class == Option
end
+ # Returns None if None, or the result of executing the given block or lambda on the contents if Some
def bind(lam = nil, &blk)
- f = lam || blk
- empty? ? self : f.call(value)
+ empty? ? self : (lam || blk).call(value)
end
include Monad
+ # Returns +true+ if None, +false+ if Some
def empty?
raise(NotImplementedError)
end
+ # Returns contents if Some, or raises NoSuchElementError if None
def get
if !empty? then value else raise NoSuchElementError end
end
+ # Returns contents if Some, or given value or result of given block or lambda if None
def get_or_else(val_or_lam = nil, &blk)
v_or_f = val_or_lam || blk
if !empty? then value else (v_or_f.respond_to?(:call) ? v_or_f.call : v_or_f) end
end
+ # Returns contents if Some, or +nil+ if None
def or_nil
get_or_else(nil)
end
end
- # TODO: Document this
+ # Represents an Option containing a value
class Some < Option
def initialize(value)
@value = value
end
- attr_reader :value
+ attr_reader :value # :nodoc:
def empty?
false
end
@@ -62,11 +100,11 @@
def to_s
"Some(#{value.to_s})"
end
end
- # TODO: Document this
+ # Represents an Option which is empty, accessed via the constant None
class NoneClass < Option
include Singleton
def empty?
true
@@ -79,24 +117,24 @@
def to_s
"None"
end
end
- # TODO: Document this
+ # Exception raised on attempts to access the value of None
class NoSuchElementError < RuntimeError; end
- # TODO: Document this
+ # Returns an Option wrapping the given value: Some if non-nil, None if nil
def Option(value)
value.nil? ? None : Some(value)
end
- # TODO: Document this
+ # Returns a Some wrapping the given value, for convenience
def Some(value)
Some.new(value)
end
- # TODO: Document this
- None = NoneClass.instance
+ # The single global instance of NoneClass, representing the empty Option
+ None = NoneClass.instance # :doc:
module_function :Option, :Some
public :Option, :Some
end