rumonade 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +8 -0
- data/CHANGELOG.rdoc +7 -0
- data/Gemfile +4 -0
- data/MIT-LICENSE.txt +20 -0
- data/README.rdoc +15 -0
- data/Rakefile +25 -0
- data/lib/rumonade.rb +41 -0
- data/lib/rumonade/array.rb +26 -0
- data/lib/rumonade/lazy_identity.rb +27 -0
- data/lib/rumonade/monad.rb +36 -0
- data/lib/rumonade/option.rb +102 -0
- data/lib/rumonade/version.rb +3 -0
- data/rumonade.gemspec +25 -0
- data/test/array_test.rb +45 -0
- data/test/lazy_identity_test.rb +16 -0
- data/test/option_test.rb +105 -0
- data/test/test_helper.rb +20 -0
- metadata +74 -0
data/.gitignore
ADDED
data/CHANGELOG.rdoc
ADDED
data/Gemfile
ADDED
data/MIT-LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2011 Marc Siegel
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
= Rumonade
|
2
|
+
=== A Ruby Monad Library, Inspired by Scala
|
3
|
+
|
4
|
+
Are you working in both the Scala and Ruby worlds, and finding that you miss some of the practical benefits
|
5
|
+
of Scala's monads in Ruby? Then Rumonade is for you.
|
6
|
+
|
7
|
+
The goal of this library is to make the most common scala monad idioms available in ruby:
|
8
|
+
* Option
|
9
|
+
* Arrays of Options / Options of Arrays
|
10
|
+
* Either
|
11
|
+
* for comprehensions
|
12
|
+
|
13
|
+
This code is in a very early state, but the Option monad is already present.
|
14
|
+
Please try it out and let me know what you think.
|
15
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
2
|
+
require "rake/testtask"
|
3
|
+
|
4
|
+
Rake::TestTask.new(:test) do |test|
|
5
|
+
test.libs << 'lib' << 'test'
|
6
|
+
test.pattern = 'test/**/*_test.rb'
|
7
|
+
end
|
8
|
+
|
9
|
+
#require "rdoc/task"
|
10
|
+
#RDoc::Task.new do |rdoc|
|
11
|
+
# rdoc.main = "README.rdoc"
|
12
|
+
# rdoc.rdoc_files.include("README.rdoc", "CHANGELOG.rdoc", "lib/**/*.rb")
|
13
|
+
#end
|
14
|
+
|
15
|
+
require 'rake/rdoctask'
|
16
|
+
Rake::RDocTask.new do |rdoc|
|
17
|
+
require File.expand_path("../lib/rumonade/version", __FILE__)
|
18
|
+
rdoc.rdoc_dir = 'rdoc'
|
19
|
+
rdoc.options << '--line-numbers' << '--inline-source'
|
20
|
+
rdoc.title = "Rumonade #{Rumonade::VERSION}"
|
21
|
+
rdoc.main = 'README.rdoc'
|
22
|
+
rdoc.rdoc_files.include('README.rdoc')
|
23
|
+
rdoc.rdoc_files.include('CHANGELOG.rdoc')
|
24
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
25
|
+
end
|
data/lib/rumonade.rb
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2011 Marc Siegel
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
5
|
+
# a copy of this software and associated documentation files (the
|
6
|
+
# "Software"), to deal in the Software without restriction, including
|
7
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
8
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
9
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
10
|
+
# the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be
|
13
|
+
# included in all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
17
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
19
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
20
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
21
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
22
|
+
#++
|
23
|
+
require "rumonade/version"
|
24
|
+
require "rumonade/monad"
|
25
|
+
require "rumonade/lazy_identity"
|
26
|
+
require "rumonade/option"
|
27
|
+
require "rumonade/array"
|
28
|
+
|
29
|
+
# = Rumonade
|
30
|
+
#
|
31
|
+
# Rumonade is a ruby module providing a scala-like system of monads, including:
|
32
|
+
# * Option
|
33
|
+
# * Array
|
34
|
+
#
|
35
|
+
# See the examples in link:files/README_rdoc.html for more insight.
|
36
|
+
#
|
37
|
+
module Rumonade
|
38
|
+
end
|
39
|
+
|
40
|
+
# bring into global namespace Monad, Option, etc
|
41
|
+
include Rumonade
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'rumonade/monad'
|
2
|
+
|
3
|
+
module Rumonade
|
4
|
+
module ArrayExtensions
|
5
|
+
module ClassMethods
|
6
|
+
def unit(value)
|
7
|
+
[value]
|
8
|
+
end
|
9
|
+
|
10
|
+
def empty
|
11
|
+
[]
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
module InstanceMethods
|
16
|
+
def bind(lam = nil, &blk)
|
17
|
+
f = lam || blk
|
18
|
+
inject([]) { |arr, elt| arr + f.call(elt).to_a }
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
Array.send(:extend, Rumonade::ArrayExtensions::ClassMethods)
|
25
|
+
Array.send(:include, Rumonade::ArrayExtensions::InstanceMethods)
|
26
|
+
Array.send(:include, Rumonade::Monad)
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# Adapted from http://stackoverflow.com/questions/2709361/monad-equivalent-in-ruby
|
2
|
+
class LazyIdentity
|
3
|
+
def initialize(lam = nil, &blk)
|
4
|
+
@lazy = lam || blk
|
5
|
+
@lazy.is_a?(Proc) || raise(ArgumentError, "not a Proc")
|
6
|
+
@lazy.arity.zero? || raise(ArgumentError, "arity must be 0, was #{@lazy.arity}")
|
7
|
+
end
|
8
|
+
|
9
|
+
attr_reader :lazy
|
10
|
+
|
11
|
+
def force
|
12
|
+
@lazy[]
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.unit(lam = nil, &blk)
|
16
|
+
LazyIdentity.new(lam || blk)
|
17
|
+
end
|
18
|
+
|
19
|
+
def bind(lam = nil, &blk)
|
20
|
+
f = lam || blk
|
21
|
+
f[@lazy]
|
22
|
+
end
|
23
|
+
|
24
|
+
def ==(other)
|
25
|
+
other.is_a?(LazyIdentity) && other.force == force
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Rumonade
|
2
|
+
# TODO: Document this
|
3
|
+
module Monad
|
4
|
+
METHODS_TO_REPLACE = [:flat_map, :flatten]
|
5
|
+
|
6
|
+
def self.included(base)
|
7
|
+
base.class_eval do
|
8
|
+
alias_method :flat_map_with_monad, :bind
|
9
|
+
|
10
|
+
# force only a few methods to be aliased to monad versions; others can stay with native or Enumerable versions
|
11
|
+
METHODS_TO_REPLACE.each do |method_name|
|
12
|
+
alias_method "#{method_name}_without_monad".to_sym, method_name if public_instance_methods.include? method_name
|
13
|
+
alias_method method_name, "#{method_name}_with_monad".to_sym
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
include Enumerable
|
19
|
+
|
20
|
+
def map(lam = nil, &blk)
|
21
|
+
bind { |v| (lam || blk).call(v) }
|
22
|
+
end
|
23
|
+
|
24
|
+
def each(lam = nil, &blk)
|
25
|
+
map(lam || blk); nil
|
26
|
+
end
|
27
|
+
|
28
|
+
def shallow_flatten
|
29
|
+
bind { |x| x.is_a?(Monad) ? x : self.class.unit(x) }
|
30
|
+
end
|
31
|
+
|
32
|
+
def flatten_with_monad
|
33
|
+
bind { |x| x.is_a?(Monad) ? x.flatten_with_monad : self.class.unit(x) }
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
require 'singleton'
|
2
|
+
require 'rumonade/monad'
|
3
|
+
|
4
|
+
module Rumonade # :nodoc:
|
5
|
+
# TODO: Document this
|
6
|
+
class Option
|
7
|
+
class << self
|
8
|
+
def unit(value)
|
9
|
+
Rumonade.Option(value)
|
10
|
+
end
|
11
|
+
|
12
|
+
def empty
|
13
|
+
None
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def initialize
|
18
|
+
raise(TypeError, "class Option is abstract; cannot be instantiated") if self.class == Option
|
19
|
+
end
|
20
|
+
|
21
|
+
def bind(lam = nil, &blk)
|
22
|
+
f = lam || blk
|
23
|
+
empty? ? self : f.call(value)
|
24
|
+
end
|
25
|
+
|
26
|
+
include Monad
|
27
|
+
|
28
|
+
def empty?
|
29
|
+
raise(NotImplementedError)
|
30
|
+
end
|
31
|
+
|
32
|
+
def get
|
33
|
+
if !empty? then value else raise NoSuchElementError end
|
34
|
+
end
|
35
|
+
|
36
|
+
def get_or_else(val_or_lam = nil, &blk)
|
37
|
+
v_or_f = val_or_lam || blk
|
38
|
+
if !empty? then value else (v_or_f.respond_to?(:call) ? v_or_f.call : v_or_f) end
|
39
|
+
end
|
40
|
+
|
41
|
+
def or_nil
|
42
|
+
get_or_else(nil)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# TODO: Document this
|
47
|
+
class Some < Option
|
48
|
+
def initialize(value)
|
49
|
+
@value = value
|
50
|
+
end
|
51
|
+
|
52
|
+
attr_reader :value
|
53
|
+
|
54
|
+
def empty?
|
55
|
+
false
|
56
|
+
end
|
57
|
+
|
58
|
+
def ==(other)
|
59
|
+
other.is_a?(Some) && other.value == value
|
60
|
+
end
|
61
|
+
|
62
|
+
def to_s
|
63
|
+
"Some(#{value.to_s})"
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# TODO: Document this
|
68
|
+
class NoneClass < Option
|
69
|
+
include Singleton
|
70
|
+
|
71
|
+
def empty?
|
72
|
+
true
|
73
|
+
end
|
74
|
+
|
75
|
+
def ==(other)
|
76
|
+
other.equal?(self.class.instance)
|
77
|
+
end
|
78
|
+
|
79
|
+
def to_s
|
80
|
+
"None"
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# TODO: Document this
|
85
|
+
class NoSuchElementError < RuntimeError; end
|
86
|
+
|
87
|
+
# TODO: Document this
|
88
|
+
def Option(value)
|
89
|
+
value.nil? ? None : Some(value)
|
90
|
+
end
|
91
|
+
|
92
|
+
# TODO: Document this
|
93
|
+
def Some(value)
|
94
|
+
Some.new(value)
|
95
|
+
end
|
96
|
+
|
97
|
+
# TODO: Document this
|
98
|
+
None = NoneClass.instance
|
99
|
+
|
100
|
+
module_function :Option, :Some
|
101
|
+
public :Option, :Some
|
102
|
+
end
|
data/rumonade.gemspec
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "rumonade/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "rumonade"
|
7
|
+
s.version = Rumonade::VERSION
|
8
|
+
s.authors = ["Marc Siegel"]
|
9
|
+
s.email = ["marc@usainnov.com"]
|
10
|
+
s.homepage = "http://github.com/ms-ati/rumonade"
|
11
|
+
s.summary = "A Scala-inspired Monad library for Ruby"
|
12
|
+
s.description = "A Scala-inspired Monad library for Ruby, aiming to share the most common idioms for folks working in both languages. Includes Option, Array, etc."
|
13
|
+
|
14
|
+
s.rubyforge_project = "rumonade"
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
|
21
|
+
# specify any dependencies here; for example:
|
22
|
+
#s.add_development_dependency "rdoc"
|
23
|
+
#s.add_development_dependency "test-unit"
|
24
|
+
#s.add_development_dependency "rr"
|
25
|
+
end
|
data/test/array_test.rb
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/test_helper')
|
2
|
+
|
3
|
+
class ArrayTest < Test::Unit::TestCase
|
4
|
+
include Rumonade
|
5
|
+
include MonadAxiomTestHelpers
|
6
|
+
|
7
|
+
def test_when_unit_returns_1_elt_array
|
8
|
+
assert_equal [1], Array.unit(1)
|
9
|
+
end
|
10
|
+
|
11
|
+
def test_when_empty_returns_empty_array
|
12
|
+
assert_equal [], Array.empty
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_monad_axioms
|
16
|
+
f = lambda { |x| Array.unit(x && x * 2) }
|
17
|
+
g = lambda { |x| Array.unit(x && x * 5) }
|
18
|
+
[1, 42].each do |value|
|
19
|
+
assert_monad_axiom_1(Array, value, f)
|
20
|
+
assert_monad_axiom_2(Array.unit(value))
|
21
|
+
assert_monad_axiom_3(Array.unit(value), f, g)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def test_flat_map_behaves_correctly
|
26
|
+
assert_equal ["FOO", "BAR"], ["foo", "bar"].flat_map { |s| [s.upcase] }
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_map_behaves_correctly
|
30
|
+
assert_equal ["FOO", "BAR"], ["foo", "bar"].map { |s| s.upcase }
|
31
|
+
end
|
32
|
+
|
33
|
+
def test_shallow_flatten_behaves_correctly
|
34
|
+
assert_equal [0, 1, [2], [[3]], [[[4]]]], [0, [1], [[2]], [[[3]]], [[[[4]]]]].shallow_flatten
|
35
|
+
assert_equal [1], [None, Some(1)].shallow_flatten
|
36
|
+
assert_equal [1, Some(2)], [None, Some(1), Some(Some(2))].shallow_flatten
|
37
|
+
assert_equal [Some(Some(None))], [Some(Some(Some(None)))].shallow_flatten
|
38
|
+
end
|
39
|
+
|
40
|
+
def test_flatten_behaves_correctly
|
41
|
+
assert_equal [0, 1, 2, 3, 4], [0, [1], [[2]], [[[3]]], [[[[4]]]]].flatten
|
42
|
+
assert_equal [1, 2], [None, Some(1), Some(Some(2))].flatten
|
43
|
+
assert_equal [], [Some(Some(Some(None)))].flatten
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'test_helper'))
|
2
|
+
|
3
|
+
class LazyIdentityTest < Test::Unit::TestCase
|
4
|
+
include Rumonade
|
5
|
+
include MonadAxiomTestHelpers
|
6
|
+
|
7
|
+
def test_monad_axioms
|
8
|
+
f = lambda { |lazy| v = lazy.call * 2; LazyIdentity.new { v } }
|
9
|
+
g = lambda { |lazy| v = lazy.call + 5; LazyIdentity.new { v } }
|
10
|
+
lazy_value = lambda { 42 } # returns 42 when called
|
11
|
+
assert_monad_axiom_1(LazyIdentity, lazy_value, f)
|
12
|
+
assert_monad_axiom_2(LazyIdentity.new(lazy_value))
|
13
|
+
assert_monad_axiom_3(LazyIdentity.new(lazy_value), f, g)
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
data/test/option_test.rb
ADDED
@@ -0,0 +1,105 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/test_helper')
|
2
|
+
|
3
|
+
class OptionTest < Test::Unit::TestCase
|
4
|
+
include Rumonade
|
5
|
+
include MonadAxiomTestHelpers
|
6
|
+
|
7
|
+
def test_when_option_with_nil_returns_none_singleton
|
8
|
+
assert_same None, Option.unit(nil)
|
9
|
+
assert_same None, Option(nil)
|
10
|
+
assert_same NoneClass.instance, None
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_when_option_with_value_returns_some
|
14
|
+
assert_equal Some(42), Option.unit(42)
|
15
|
+
assert_equal Some(42), Option(42)
|
16
|
+
assert_equal Some(42), Some.new(42)
|
17
|
+
assert_not_equal None, Some(nil)
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_when_option_constructor_raises
|
21
|
+
assert_raise(TypeError) { Option.new }
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_monad_axioms
|
25
|
+
f = lambda { |x| Option(x && x * 2) }
|
26
|
+
g = lambda { |x| Option(x && x * 5) }
|
27
|
+
[nil, 42].each do |value|
|
28
|
+
assert_monad_axiom_1(Option, value, f)
|
29
|
+
assert_monad_axiom_2(Option(value))
|
30
|
+
assert_monad_axiom_3(Option(value), f, g)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_when_empty_returns_none
|
35
|
+
assert_equal None, Option.empty
|
36
|
+
end
|
37
|
+
|
38
|
+
def test_when_value_on_some_returns_value_but_on_none_raises
|
39
|
+
assert_equal "foo", Some("foo").value
|
40
|
+
assert_raise(NoMethodError) { None.value }
|
41
|
+
end
|
42
|
+
|
43
|
+
def test_when_get_on_some_returns_value_but_on_none_raises
|
44
|
+
assert_equal "foo", Some("foo").get
|
45
|
+
assert_raise(NoSuchElementError) { None.get }
|
46
|
+
end
|
47
|
+
|
48
|
+
def test_when_get_or_else_on_some_returns_value_but_on_none_returns_value_or_executes_block_or_lambda
|
49
|
+
assert_equal "foo", Some("foo").get_or_else("bar")
|
50
|
+
assert_equal "bar", None.get_or_else("bar")
|
51
|
+
assert_equal "blk", None.get_or_else { "blk" }
|
52
|
+
assert_equal "lam", None.get_or_else(lambda { "lam"} )
|
53
|
+
end
|
54
|
+
|
55
|
+
def test_when_or_nil_on_some_returns_value_but_on_none_returns_nil
|
56
|
+
assert_equal 123, Some(123).or_nil
|
57
|
+
assert_nil None.or_nil
|
58
|
+
end
|
59
|
+
|
60
|
+
def test_flat_map_behaves_correctly
|
61
|
+
assert_equal Some("FOO"), Some("foo").flat_map { |s| Some(s.upcase) }
|
62
|
+
assert_equal None, None.flat_map { |s| Some(s.upcase) }
|
63
|
+
end
|
64
|
+
|
65
|
+
def test_map_behaves_correctly
|
66
|
+
assert_equal "FOO", Some("foo").map { |s| s.upcase }
|
67
|
+
assert_equal None, None.map { |s| s.upcase }
|
68
|
+
end
|
69
|
+
|
70
|
+
def test_shallow_flatten_behaves_correctly
|
71
|
+
assert_equal Some(Some(1)), Some(Some(Some(1))).shallow_flatten
|
72
|
+
assert_equal None, Some(None).shallow_flatten
|
73
|
+
assert_equal Some(1), Some(1).shallow_flatten
|
74
|
+
assert_equal [None, Some(1)], Some([None, Some(1)]).shallow_flatten
|
75
|
+
end
|
76
|
+
|
77
|
+
def test_flatten_behaves_correctly
|
78
|
+
assert_equal Some(1), Some(Some(Some(1))).flatten
|
79
|
+
assert_equal None, Some(None).flatten
|
80
|
+
assert_equal Some(1), Some(1).flatten
|
81
|
+
assert_equal [1], Some([None, Some(1)]).flatten
|
82
|
+
end
|
83
|
+
|
84
|
+
def test_to_s_behaves_correctly
|
85
|
+
assert_equal "Some(1)", Some(1).to_s
|
86
|
+
assert_equal "None", None.to_s
|
87
|
+
assert_equal "Some(Some(None))", Some(Some(None)).to_s
|
88
|
+
end
|
89
|
+
|
90
|
+
def test_each_behaves_correctly
|
91
|
+
vals = [None, Some(42)].inject([]) { |arr, opt| assert_nil(opt.each { |val| arr << val }); arr }
|
92
|
+
assert_equal [42], vals
|
93
|
+
end
|
94
|
+
|
95
|
+
def test_enumerable_methods_are_available
|
96
|
+
assert Some(1).all? { |v| v < 10 }
|
97
|
+
assert !Some(1).all? { |v| v > 10 }
|
98
|
+
assert None.all? { |v| v > 10 }
|
99
|
+
end
|
100
|
+
|
101
|
+
def test_to_a_behaves_correctly
|
102
|
+
assert_equal [1], Some(1).to_a
|
103
|
+
assert_equal [], None.to_a
|
104
|
+
end
|
105
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
$:.unshift(File.dirname(File.dirname(File.expand_path(__FILE__))) + '/lib')
|
2
|
+
require "rubygems"
|
3
|
+
require "test/unit"
|
4
|
+
require "rr"
|
5
|
+
require "rumonade"
|
6
|
+
|
7
|
+
# see http://stackoverflow.com/questions/2709361/monad-equivalent-in-ruby
|
8
|
+
module MonadAxiomTestHelpers
|
9
|
+
def assert_monad_axiom_1(monad_class, value, f)
|
10
|
+
assert_equal f[value], monad_class.unit(value).bind(f)
|
11
|
+
end
|
12
|
+
|
13
|
+
def assert_monad_axiom_2(monad)
|
14
|
+
assert_equal monad, monad.bind(lambda { |v| monad.class.unit(v) })
|
15
|
+
end
|
16
|
+
|
17
|
+
def assert_monad_axiom_3(monad, f, g)
|
18
|
+
assert_equal monad.bind(f).bind(g), monad.bind { |x| f[x].bind(g) }
|
19
|
+
end
|
20
|
+
end
|
metadata
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rumonade
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: 0.1.0
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Marc Siegel
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2011-09-17 00:00:00 Z
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description: A Scala-inspired Monad library for Ruby, aiming to share the most common idioms for folks working in both languages. Includes Option, Array, etc.
|
17
|
+
email:
|
18
|
+
- marc@usainnov.com
|
19
|
+
executables: []
|
20
|
+
|
21
|
+
extensions: []
|
22
|
+
|
23
|
+
extra_rdoc_files: []
|
24
|
+
|
25
|
+
files:
|
26
|
+
- .gitignore
|
27
|
+
- CHANGELOG.rdoc
|
28
|
+
- Gemfile
|
29
|
+
- MIT-LICENSE.txt
|
30
|
+
- README.rdoc
|
31
|
+
- Rakefile
|
32
|
+
- lib/rumonade.rb
|
33
|
+
- lib/rumonade/array.rb
|
34
|
+
- lib/rumonade/lazy_identity.rb
|
35
|
+
- lib/rumonade/monad.rb
|
36
|
+
- lib/rumonade/option.rb
|
37
|
+
- lib/rumonade/version.rb
|
38
|
+
- rumonade.gemspec
|
39
|
+
- test/array_test.rb
|
40
|
+
- test/lazy_identity_test.rb
|
41
|
+
- test/option_test.rb
|
42
|
+
- test/test_helper.rb
|
43
|
+
homepage: http://github.com/ms-ati/rumonade
|
44
|
+
licenses: []
|
45
|
+
|
46
|
+
post_install_message:
|
47
|
+
rdoc_options: []
|
48
|
+
|
49
|
+
require_paths:
|
50
|
+
- lib
|
51
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
52
|
+
none: false
|
53
|
+
requirements:
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: "0"
|
57
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
58
|
+
none: false
|
59
|
+
requirements:
|
60
|
+
- - ">="
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: "0"
|
63
|
+
requirements: []
|
64
|
+
|
65
|
+
rubyforge_project: rumonade
|
66
|
+
rubygems_version: 1.8.8
|
67
|
+
signing_key:
|
68
|
+
specification_version: 3
|
69
|
+
summary: A Scala-inspired Monad library for Ruby
|
70
|
+
test_files:
|
71
|
+
- test/array_test.rb
|
72
|
+
- test/lazy_identity_test.rb
|
73
|
+
- test/option_test.rb
|
74
|
+
- test/test_helper.rb
|