monadic 0.0.1 → 0.0.2

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/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