matilda-maybe 0.3.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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: dfedc2eb085cf84abaaf05050cee7cfc895ceaa0
4
+ data.tar.gz: 9ed28c975b9844f0075515bdb07f95fdbdea8bb5
5
+ SHA512:
6
+ metadata.gz: d2e91c2a516f70cd7ab6b056aa8e32a69b75dd503f0bbefd8326b6d49a66e3453b006f6beba49a8e783978e3ff9007e1942d524ae5369b5e93d4217c717b8d10
7
+ data.tar.gz: 0a147b5b11ad2d7d7d7d15b7892418f9ea90c48c4e6b68947f72f5b043a3289f6448addece82cdfcb859c6d29a1573d8f9ad3e5d140fe400be08f6c81ea74f67
data/.gitignore ADDED
@@ -0,0 +1,2 @@
1
+ .DS_Store
2
+ coverage
data/.travis.yml ADDED
@@ -0,0 +1,5 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
4
+ - 2.0.0
5
+ - jruby-19mode
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'coveralls', require: false
4
+
5
+ group :test do
6
+ gem 'rake'
7
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,27 @@
1
+ GEM
2
+ remote: https://rubygems.org/
3
+ specs:
4
+ colorize (0.5.8)
5
+ coveralls (0.6.7)
6
+ colorize
7
+ multi_json (~> 1.3)
8
+ rest-client
9
+ simplecov (>= 0.7)
10
+ thor
11
+ mime-types (1.23)
12
+ multi_json (1.7.3)
13
+ rake (10.0.3)
14
+ rest-client (1.6.7)
15
+ mime-types (>= 1.16)
16
+ simplecov (0.7.1)
17
+ multi_json (~> 1.0)
18
+ simplecov-html (~> 0.7.1)
19
+ simplecov-html (0.7.1)
20
+ thor (0.18.1)
21
+
22
+ PLATFORMS
23
+ ruby
24
+
25
+ DEPENDENCIES
26
+ coveralls
27
+ rake
data/LICENSE.md ADDED
@@ -0,0 +1,7 @@
1
+ The MIT License (MIT) Copyright (c) Callum Stott, http://www.seadowg.com
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,95 @@
1
+ # ruby-maybe
2
+
3
+ [![Build Status](https://travis-ci.org/seadowg/ruby-maybe.png?branch=master)](https://travis-ci.org/seadowg/ruby-maybe)
4
+
5
+ ![](http://f.cl.ly/items/2o2A3k1N2d3a1b0V3V0T/maybe.png)
6
+
7
+ ## Installation
8
+
9
+ Either include in your Gemfile:
10
+
11
+ gem 'ruby-maybe'
12
+
13
+ Or, install for your system:
14
+
15
+ > gem install ruby-maybe
16
+
17
+ ## Usage
18
+
19
+ ### The Basics
20
+
21
+ 'Maybe' not 'Maeby'. This is an implementation of the 'Maybe' or 'Option' monad similar to that used
22
+ [Haskell](http://www.haskell.org/haskellwiki/Maybe). Monads provide a
23
+ safe way to create non deterministic programs (for example, when not all
24
+ values are known at compile time). The Maybe monad allows programmers to
25
+ deal with values that may or may not be undefined.
26
+
27
+ Imagine we are access accessing an array via a method:
28
+
29
+ array = [1,2,3]
30
+
31
+ def access(n)
32
+ array[n]
33
+ end
34
+
35
+ access(1) # => 2
36
+ access(5) # => nil
37
+
38
+ Now what if we want to take some index and add `5` to the array value
39
+ stored there?
40
+
41
+ access(5) + 5 # => NoMethodError: undefined method `+' for nil:NilClass
42
+
43
+ Hmmm, that fails pretty miserably. We can solve this with `Maybe`. Lets
44
+ rewrite `access`:
45
+
46
+ def access(n)
47
+ if array[n]
48
+ Just.new(array[n])
49
+ else
50
+ Nothing
51
+ end
52
+ end
53
+
54
+ `Nothing` and `Just` used here are both instances of `Maybe`. This means
55
+ that they will both respond to the `bind` method:
56
+
57
+ access(1).bind { |val| Just.new(val + 5) } # => Just.new(7)
58
+ access(5).bind { |val| Just.new(val + 5) } # => Nothing.new
59
+
60
+ For instances of `Just`, `bind` will execute the passed block with
61
+ respect to its contained value and for `Nothing` it will skip the block
62
+ and simply return another instance of `Nothing`. This allows a neat
63
+ mechanism for dealing with non determinitic methods such as `access`
64
+ without having them throw exceptions or simply return `nil`.
65
+
66
+ `Maybe` is a very basic monad and at first might not seem that powerful
67
+ but after using it instead of the more verbose control flow it replaces
68
+ you might just learn to love it.
69
+
70
+ ### Method Lifting
71
+
72
+ Ok so using `bind` to operate on Maybe is all well and good, but what if
73
+ you want to add three Maybe's together:
74
+
75
+ x.bind { |x_val|
76
+ y.bind { |y_val|
77
+ z.bind { |z_val|
78
+ Just.new(x + y + z)
79
+ }
80
+ }
81
+ }
82
+
83
+ Yeah... I don't think so. In languages like Haskell we can use [Applicative Functors](http://learnyouahaskell.com/functors-applicative-functors-and-monoids)
84
+ to deal with making expressions like the above less verbose. You can go read about them but that's not really important.
85
+ With ruby-maybe methods on the contained object in a Maybe can be lifted to operate on the Maybe:
86
+
87
+ Just.new(5) + Just.new(6) # => Just.new(11)
88
+ Just.new("OMG").downcase # => Just.new("omg")
89
+ Just.new([1,2]).inject(Just.new(0), :+) # => Just.new(3)
90
+
91
+ All operations can be lifted like this and you can mix and match actual values and Maybes in the arguments. This also
92
+ works for Nothing:
93
+
94
+ Nothing.new + Just.new(5) # => Nothing.new
95
+ Just.new(0) * Nothing.new / Just.new(1) # => Nothing.new
data/Rakefile ADDED
@@ -0,0 +1,9 @@
1
+ require 'rake/testtask'
2
+
3
+ task :default => :test
4
+
5
+ Rake::TestTask.new do |t|
6
+ t.libs << "spec"
7
+ t.test_files = FileList['spec/*_spec.rb']
8
+ t.verbose = true
9
+ end
@@ -0,0 +1,3 @@
1
+ require 'matilda-maybe/maybe'
2
+ require 'matilda-maybe/just'
3
+ require 'matilda-maybe/nothing'
@@ -0,0 +1,46 @@
1
+ class Just < Maybe
2
+ def method_missing(method_name, *args, &block)
3
+ values = args.map { |arg|
4
+ return Nothing.new if arg == Nothing.new
5
+ arg.kind_of?(Just) ? arg.value : arg
6
+ }
7
+
8
+ Just.new(@value.public_send(method_name, *values, &block))
9
+ end
10
+
11
+ def initialize(value)
12
+ @value = value
13
+ end
14
+
15
+ def bind(&block)
16
+ computed = block.call(@value)
17
+ warn("Not returning a Maybe from #bind is really bad form...") unless computed.kind_of?(Maybe)
18
+ computed
19
+ end
20
+
21
+ def map(&block)
22
+ Just.new(block.call(@value))
23
+ end
24
+
25
+ def or(*args, &block)
26
+ self
27
+ end
28
+
29
+ def get(*args, &block)
30
+ @value
31
+ end
32
+
33
+ def ==(object)
34
+ if object.class == Just
35
+ object.value == self.value
36
+ else
37
+ false
38
+ end
39
+ end
40
+
41
+ protected
42
+
43
+ def value
44
+ @value
45
+ end
46
+ end
@@ -0,0 +1,9 @@
1
+ class Maybe
2
+ def self.from(value)
3
+ if !value.nil?
4
+ Just.new(value)
5
+ else
6
+ Nothing.new
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,37 @@
1
+ class Nothing < Maybe
2
+ def method_missing(method_name, *args, &block)
3
+ Nothing.new
4
+ end
5
+
6
+ def bind
7
+ Nothing.new
8
+ end
9
+
10
+ def map
11
+ Nothing.new
12
+ end
13
+
14
+ def or(*args, &block)
15
+ if args.empty?
16
+ block.call
17
+ else
18
+ args.first
19
+ end
20
+ end
21
+
22
+ def get(*args, &block)
23
+ if args.empty?
24
+ block.call
25
+ else
26
+ args.first
27
+ end
28
+ end
29
+
30
+ def ==(object)
31
+ if object.class == Nothing
32
+ true
33
+ else
34
+ false
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,15 @@
1
+ lib = File.expand_path('../lib/', __FILE__)
2
+ $:.unshift lib unless $:.include?(lib)
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = "matilda-maybe"
6
+ s.version = "0.3.0"
7
+ s.platform = Gem::Platform::RUBY
8
+ s.authors = ["Callum Stott"]
9
+ s.email = ["callum@seadowg"]
10
+ s.summary = "Maybe monad implementation for Ruby"
11
+ s.license = 'MIT'
12
+
13
+ s.require_paths = ['lib']
14
+ s.files = `git ls-files`.split("\n")
15
+ end
@@ -0,0 +1,31 @@
1
+ require 'spec_helper'
2
+ require 'matilda-maybe'
3
+
4
+ describe "Integration Specs" do
5
+ it "works as expected" do
6
+ Just.new(5).bind { |val|
7
+ Just.new(val).map { |val|
8
+ val * 3
9
+ }
10
+ }.bind { |val|
11
+ if (val > 10)
12
+ Nothing.new
13
+ else
14
+ Just.new(val) + 1
15
+ end
16
+ }.must_equal(Nothing.new)
17
+ end
18
+
19
+ it "has the correct interface" do
20
+ has_method(:bind)
21
+ has_method(:map)
22
+ has_method(:or)
23
+ has_method(:get)
24
+ has_method(:==)
25
+ end
26
+ end
27
+
28
+ def has_method(name)
29
+ assert(Just.method_defined?(name), "Just does not respond to #{name}")
30
+ assert(Nothing.method_defined?(name), "Nothing does not respond to #{name}")
31
+ end
data/spec/just_spec.rb ADDED
@@ -0,0 +1,57 @@
1
+ require 'spec_helper'
2
+ require 'matilda-maybe'
3
+
4
+ describe "Just" do
5
+ describe "#bind" do
6
+ it "applys the passed block to its boxed value" do
7
+ Just.new(5).bind { |val| Just.new(val * 2) }.must_equal Just.new(10)
8
+ end
9
+ end
10
+
11
+ describe "#or" do
12
+ it "ignores arg and returns self" do
13
+ (Just.new(5).or { 0 }).must_equal Just.new(5)
14
+ end
15
+ end
16
+
17
+ describe "#get" do
18
+ it "ignores the arguments and returns the value" do
19
+ (Just.new(5).get { 0 }).must_equal 5
20
+ Just.new(5).get(0).must_equal 5
21
+ end
22
+ end
23
+
24
+ describe "#==" do
25
+ it "returns false if the passed object is not a Just" do
26
+ (Just.new(5) == 5).must_equal false
27
+ end
28
+
29
+ it "returns true if the passed object is a Just with the same value" do
30
+ (Just.new(5) == Just.new(5)).must_equal true
31
+ end
32
+ end
33
+
34
+ describe "method lifting" do
35
+ it "allows using a method for the contained type" do
36
+ just = Just.new(5)
37
+ just.+(5).must_equal(Just.new(10))
38
+ end
39
+
40
+ it "allows passed arguments to be Maybe instances" do
41
+ just = Just.new(5)
42
+ just.+(Just.new(5)).must_equal(Just.new(10))
43
+ end
44
+
45
+ it "returns Nothing if any argument is Nothing" do
46
+ just = Just.new("Hello")
47
+ just.slice(Just.new(0), Nothing.new).must_equal(Nothing.new)
48
+ end
49
+
50
+ it "passes blocks correctly" do
51
+ count = 0
52
+ just = Just.new(5)
53
+ just.times { |i| count += i }
54
+ count.must_equal(10)
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,15 @@
1
+ require 'spec_helper'
2
+ require 'matilda-maybe'
3
+
4
+ describe "Maybe" do
5
+ describe "#from" do
6
+ it "returns Nothing if passed nil" do
7
+ Maybe.from(nil).must_equal Nothing.new
8
+ end
9
+
10
+ it "returns Just if passed non-nil value" do
11
+ Maybe.from(false).must_equal Just.new(false)
12
+ Maybe.from(11).must_equal Just.new(11)
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,56 @@
1
+ require 'spec_helper'
2
+ require 'matilda-maybe'
3
+
4
+ describe "Nothing" do
5
+ describe "#bind" do
6
+ it "does not execute the passed block" do
7
+ executed = false
8
+ Nothing.new.bind { |val| executed = true }
9
+ executed.must_equal false
10
+ end
11
+
12
+ it "returns a Nothing" do
13
+ Nothing.new.bind { |val| val }.kind_of?(Nothing).must_equal true
14
+ end
15
+ end
16
+
17
+ describe "#map" do
18
+ it "returns Nothing" do
19
+ Nothing.new.map { |val| val }.must_equal Nothing.new
20
+ end
21
+ end
22
+
23
+ describe "#or" do
24
+ it "evaluates arg, returning result" do
25
+ (Nothing.new.or { Just.new(5) }).must_equal Just.new(5)
26
+ Nothing.new.or(Just.new(5)).must_equal Just.new(5)
27
+ end
28
+
29
+ it "chain or calls, returning last value" do
30
+ (Nothing.new.or { Nothing.new.or { Just.new(10) } }).must_equal Just.new(10)
31
+ end
32
+ end
33
+
34
+ describe "#get" do
35
+ it "evaluates arg, returning result" do
36
+ (Nothing.new.get { 5 }).must_equal 5
37
+ Nothing.new.get(5).must_equal 5
38
+ end
39
+ end
40
+
41
+ describe "#==" do
42
+ it "returns false if the passed object is not a Nothing" do
43
+ (Nothing.new == 5).must_equal false
44
+ end
45
+
46
+ it "returns true if the passed object is a Nothing" do
47
+ (Nothing.new == Nothing.new).must_equal true
48
+ end
49
+ end
50
+
51
+ describe "method lifting" do
52
+ it "returns Nothing for any method call" do
53
+ Nothing.new.missing.must_equal(Nothing.new)
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,4 @@
1
+ require 'coveralls'
2
+ Coveralls.wear!
3
+
4
+ require 'minitest/autorun'
metadata ADDED
@@ -0,0 +1,61 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: matilda-maybe
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.3.0
5
+ platform: ruby
6
+ authors:
7
+ - Callum Stott
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-08-07 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description:
14
+ email:
15
+ - callum@seadowg
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - .gitignore
21
+ - .travis.yml
22
+ - Gemfile
23
+ - Gemfile.lock
24
+ - LICENSE.md
25
+ - README.md
26
+ - Rakefile
27
+ - lib/matilda-maybe.rb
28
+ - lib/matilda-maybe/just.rb
29
+ - lib/matilda-maybe/maybe.rb
30
+ - lib/matilda-maybe/nothing.rb
31
+ - matilda-maybe.gemspec
32
+ - spec/integration_spec.rb
33
+ - spec/just_spec.rb
34
+ - spec/maybe_spec.rb
35
+ - spec/nothing_spec.rb
36
+ - spec/spec_helper.rb
37
+ homepage:
38
+ licenses:
39
+ - MIT
40
+ metadata: {}
41
+ post_install_message:
42
+ rdoc_options: []
43
+ require_paths:
44
+ - lib
45
+ required_ruby_version: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - '>='
48
+ - !ruby/object:Gem::Version
49
+ version: '0'
50
+ required_rubygems_version: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ requirements: []
56
+ rubyforge_project:
57
+ rubygems_version: 2.0.0
58
+ signing_key:
59
+ specification_version: 4
60
+ summary: Maybe monad implementation for Ruby
61
+ test_files: []