monadic 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -7,7 +7,7 @@ See also http://en.wikipedia.org/wiki/Monad
7
7
  Planned are the following monadics:
8
8
 
9
9
  - Option (Maybe in Haskell)
10
- - Either
10
+ - Either *planned
11
11
 
12
12
  ## Installation
13
13
 
@@ -25,7 +25,88 @@ Or install it yourself as:
25
25
 
26
26
  ## Usage
27
27
 
28
- TODO: Write usage instructions here
28
+ ### Option
29
+ Is an optional type, which helps to handle error conditions gracefully. The one thing to remember about option is: 'What goes into the Option, stays in the Option'.
30
+
31
+ Basic usage examples:
32
+
33
+ # handling nil (None serves as NullObject)
34
+ obj = nil
35
+ Option(obj).a.b.c == None
36
+
37
+ # None stays None
38
+ Option(nil)._ == "None"
39
+ "#{Option(nil)}" == "None"
40
+ Option(nil)._("unknown") == "unknown"
41
+ Option(nil).none? == true
42
+ Option(nil).empty? == true
43
+ Option(nil).truly? == false
44
+
45
+ # Some stays Some, unless you unbox it
46
+ Option('FOO').downcase == Some('foo')
47
+ Option('FOO').downcase.value == "foo"
48
+ Option('FOO').downcase._ == "foo"
49
+ Option('foo').none? == false
50
+ Option('foo').empty? == false
51
+ Option('foo').truly? == true
52
+
53
+ Map, select:
54
+
55
+ Option(123).map { |value| User.find(value) } == Option(someUser) # if user found
56
+ Option(0).map { |value| User.find(value) } == None # if user not found
57
+ Option([1,2]).map { |value| value.to_s } == Option(["1", "2"]) # for all Enumerables
58
+
59
+ Option('foo').select { |value| value.start_with?('f') } == Some('foo')
60
+ Option('bar').select { |value| value.start_with?('f') } == None
61
+
62
+ Treat it like an array:
63
+
64
+ Option(123).to_a == [123]
65
+ Option([123, 456]).to_a == [123, 456]
66
+ Option(nil) == []
67
+
68
+ Falsey values (kind-of) examples:
69
+
70
+ user = Option(User.find(123))
71
+ user.value('You are not logged in') { |user| "You are logged in as #{user.name}" }.should == 'You are logged in as foo'
72
+
73
+ if user != nil
74
+ "You are logged in as foo"
75
+ else
76
+ "You are not logged in"
77
+
78
+ user.subscribed? # always true
79
+ user.subscribed?.truly? # true if subscribed is true
80
+ user.subscribed?.value(false) # same as above
81
+ user.subscribed?.or(false) # same as above
82
+
83
+ Remember! an Option is never false, if you want to know if it is false, call `#none?` of `#truly?`
84
+
85
+ `#truly?` will return true or false, always.
86
+
87
+ Slug example
88
+
89
+ # instead of
90
+ def slug(title)
91
+ if title
92
+ title.strip.downcase.tr_s('^[a-z0-9]', '-')
93
+ end
94
+ end
95
+
96
+ # or
97
+
98
+ def slug(title)
99
+ title && title.strip.downcase.tr_s('^[a-z0-9]', '-')
100
+ end
101
+
102
+ # do it with a default
103
+ def slug(title)
104
+ Option(title).strip.downcase.tr_s('^[a-z0-9]', '-')._('unknown-title')
105
+ end
106
+
107
+
108
+ see also [Option Type ](http://devblog.avdi.org/2011/05/30/null-objects-and-falsiness/) and
109
+ [NullObject and Falsiness by @avdi](http://devblog.avdi.org/2011/05/30/null-objects-and-falsiness/)
29
110
 
30
111
  ## Contributing
31
112
 
@@ -0,0 +1,89 @@
1
+ require 'singleton'
2
+
3
+ def Option(value)
4
+ return None if value.nil? || (value.respond_to?(:empty?) && value.empty?)
5
+ return Some.new(value)
6
+ end
7
+ alias :Some :Option
8
+
9
+ class Some
10
+ def initialize(value)
11
+ @value = value
12
+ end
13
+
14
+ def none?
15
+ false
16
+ end
17
+ alias :empty? :none?
18
+
19
+ def truly?
20
+ @value == true
21
+ end
22
+
23
+ def else(default)
24
+ return default if none?
25
+ return self
26
+ end
27
+
28
+ def to_ary
29
+ return [@value].flatten if @value.respond_to? :flatten
30
+ return [@value]
31
+ end
32
+ alias :to_a :to_ary
33
+
34
+ def map(&block)
35
+ return Option(@value.map(&block)) if @value.is_a?(Enumerable)
36
+ return Option(block.call)
37
+ end
38
+
39
+ def select(&block)
40
+ return Option(@value.select(&block)) if @value.is_a?(Enumerable)
41
+ return None unless block.call(@value)
42
+ return self
43
+ end
44
+
45
+ def value(default=None, &block)
46
+ return default if none?
47
+ return block.call(@value) if block_given?
48
+ return @value
49
+ end
50
+ alias :or :value
51
+ alias :_ :value
52
+
53
+ def method_missing(m, *args)
54
+ Option(@value.__send__(m, *args))
55
+ end
56
+
57
+ def ==(other)
58
+ return false unless other.is_a? Some
59
+ @value == other.instance_variable_get(:@value)
60
+ end
61
+ end
62
+
63
+ class None
64
+ class << self
65
+ def method_missing(m, *args)
66
+ self
67
+ end
68
+
69
+ def value(default=self)
70
+ default
71
+ end
72
+ alias :or :value
73
+ alias :_ :value
74
+
75
+ def to_ary
76
+ []
77
+ end
78
+ alias :to_a :to_ary
79
+
80
+ def none?
81
+ true
82
+ end
83
+ alias :empty? :none?
84
+
85
+ def truly?
86
+ false
87
+ end
88
+ end
89
+ end
@@ -1,3 +1,3 @@
1
1
  module Monadic
2
- VERSION = "0.0.1"
2
+ VERSION = "0.0.2"
3
3
  end
data/lib/monadic.rb CHANGED
@@ -1,5 +1,5 @@
1
- require "monadic/version"
1
+ require 'monadic/version'
2
2
 
3
- module Monadic
4
- # Your code goes here...
5
- end
3
+ $LOAD_PATH << File.expand_path('..', __FILE__)
4
+
5
+ require 'monadic/option'
data/spec/option_spec.rb CHANGED
@@ -1,7 +1,108 @@
1
1
  require 'spec_helper'
2
2
 
3
- class Option
4
- end
3
+ describe 'Option' do
4
+ it 'nil as value always returns None()' do
5
+ Option(nil).a.b.c.should == None
6
+ end
7
+
8
+ it 'None stays None' do
9
+ Option(nil)._.should == None
10
+ Option(nil).none?.should be_true
11
+ Option(nil).empty?.should be_true
12
+ end
13
+
14
+ it 'Some stays Some' do
15
+ Option('foo').should be_kind_of(Some)
16
+ Option('foo').none?.should be_false
17
+ Option('foo').empty?.should be_false
18
+ end
19
+
20
+ it 'None to_s is "None"' do
21
+ option = Option(nil)
22
+ "#{option}".should == "None"
23
+ "#{option._}".should == "None"
24
+ end
25
+
26
+ it '[] as value always returns None()' do
27
+ Option([]).a.should == None
28
+ end
29
+
30
+ it 'calling methods on Option always returns an Option with the transformed value' do
31
+ Option('FOO').downcase.should == Some('foo')
32
+ end
33
+
34
+ it 'returns the value of an option' do
35
+ Option('foo').value.should == 'foo'
36
+ Option('foo')._.should == 'foo'
37
+ end
38
+
39
+ it 'returns the value of an option with a default, in case value is None' do
40
+ Option(nil).value('bar').should == 'bar'
41
+ Option(nil)._('bar').should == 'bar'
42
+ end
43
+
44
+ it 'returns the value and not the default if it is Some' do
45
+ Option('FOO').downcase.value('bar').should == 'foo'
46
+ Option('FOO').downcase._('bar').should == 'foo'
47
+ end
48
+
49
+ it 'returns the value applied to a block if it is Some' do
50
+ Option('foo').value('bar') { |val| "You are logged in as #{val}" }.should == 'You are logged in as foo'
51
+ Option(nil).value('You are not logged in') { |val| "You are logged in as #{val}" }.should == 'You are not logged in'
52
+ end
53
+
54
+ it 'is never falsey' do
55
+ Option('foo').should_not be_false
56
+ Option(nil).should_not be_false
57
+ end
58
+
59
+ it 'tells you if it is none when calling #none?' do
60
+ Option('foo').none?.should be_false
61
+ Option(nil).none?.should be_true
62
+ end
63
+
64
+ class User
65
+ attr_reader :name
66
+ def initialize(name)
67
+ @name = name
68
+ end
69
+ def subscribed?
70
+ true
71
+ end
72
+ end
73
+
74
+ it 'allows to use a block with value and _' do
75
+ user = Option(User.new('foo'))
76
+ user.value('You are not logged in') { |user| "You are logged in as #{user.name}" }.should == 'You are logged in as foo'
77
+ end
78
+
79
+ it 'handles (kind-of) falsey values' do
80
+ user = Option(User.new('foo'))
81
+ user.subscribed?.or(false).should be_true
82
+ user.subscribed?.truly?.should be_true
83
+
84
+ user = Option(nil)
85
+ user.subscribed?.or(false).should be_false
86
+ user.subscribed?.truly?.should be_false
87
+ end
88
+
89
+ it 'allows to use map' do
90
+ Option(nil).map { |e| Hash.new(:key => e) }.should == None
91
+ Option('foo').map { |e| Hash.new(:key => e) }.should == Some(Hash.new(:key => 'foo'))
92
+ Option([1,2]).map { |e| e.to_s }.should == Some(["1", "2"])
93
+ end
94
+
95
+ it 'allows to use select' do
96
+ Option('foo').select { |e| e.start_with?('f') }.should == Some('foo')
97
+ Option('bar').select { |e| e.start_with?('f') }.should == None
98
+ Option(nil).select { |e| e.never_called }.should == None
99
+ Option([1, 2]).select { |e| e == 1 }.should == Some([1])
100
+ end
101
+
102
+ it 'acts as an array' do
103
+ Option('foo').to_a.should == ['foo']
104
+ Option(['foo', 'bar']).to_a.should == ['foo', 'bar']
105
+ Option(nil).to_a.should == []
106
+ end
5
107
 
6
- describe Option do
7
108
  end
data/spec/spec_helper.rb CHANGED
@@ -1,2 +1,4 @@
1
1
  require 'bundler/setup'
2
2
  Bundler.require(:default, :test)
3
+
4
+ require File.expand_path('../../lib/monadic', __FILE__)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: monadic
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -106,6 +106,7 @@ files:
106
106
  - README.md
107
107
  - Rakefile
108
108
  - lib/monadic.rb
109
+ - lib/monadic/option.rb
109
110
  - lib/monadic/version.rb
110
111
  - monadic.gemspec
111
112
  - spec/option_spec.rb