monadic 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG.md ADDED
@@ -0,0 +1,11 @@
1
+ # Changelog
2
+
3
+ ## v0.0.3
4
+
5
+ `Some#map` and `Some#select` accept proc and block, you can now use:
6
+
7
+ Option("FOO").map(&:downcase) # NEW
8
+ Option("FOO").map { |e| e.downcase } # old
9
+ Option("FOO").downcase # old
10
+
11
+ Removed `#none?`, please use `#empty?` instead.
data/README.md CHANGED
@@ -1,32 +1,32 @@
1
1
  # Monadic
2
2
 
3
- is a gem to help dealing with exceptional situations, it comes from the sphere of functional programming and bringing the goodies I have come to love in Scala to my ruby projects (hence I will be using more Scala like constructs than Haskell).
3
+ helps dealing with exceptional situations, it comes from the sphere of functional programming and bringing the goodies I have come to love in Scala to my ruby projects (hence I will be using more Scala like constructs than Haskell).
4
4
 
5
5
  See also http://en.wikipedia.org/wiki/Monad
6
6
 
7
- Planned are the following monadics:
7
+ We have the following monadics:
8
8
 
9
9
  - Option (Maybe in Haskell)
10
10
  - Either *planned
11
11
 
12
- ## Installation
13
-
14
- Add this line to your application's Gemfile:
15
-
16
- gem 'monadic'
12
+ What's the point of using monads in ruby? To me it started with having a safe way to deal with nil objects and other exceptions.
13
+ Thus you contain the erroneous behaviour within a monad - an indivisible, impenetrable unit.
17
14
 
18
- And then execute:
15
+ ## Usage
19
16
 
20
- $ bundle
17
+ ### Option
18
+ 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'.
21
19
 
22
- Or install it yourself as:
23
20
 
24
- $ gem install monadic
21
+ Option(User.find(123)).name._ # ._ is a shortcut for .value
25
22
 
26
- ## Usage
23
+ # if you prefer the alias Maybe instead of option
24
+ Maybe(User.find(123)).name._
27
25
 
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'.
26
+ # confidently diving into nested hashes
27
+ Maybe({})[:a][:b][:c] == None
28
+ Maybe({})[:a][:b][:c].value('unknown') == None
29
+ Maybe(a: 1)[:a]._ == 1
30
30
 
31
31
  Basic usage examples:
32
32
 
@@ -38,7 +38,6 @@ Basic usage examples:
38
38
  Option(nil)._ == "None"
39
39
  "#{Option(nil)}" == "None"
40
40
  Option(nil)._("unknown") == "unknown"
41
- Option(nil).none? == true
42
41
  Option(nil).empty? == true
43
42
  Option(nil).truly? == false
44
43
 
@@ -46,7 +45,6 @@ Basic usage examples:
46
45
  Option('FOO').downcase == Some('foo')
47
46
  Option('FOO').downcase.value == "foo"
48
47
  Option('FOO').downcase._ == "foo"
49
- Option('foo').none? == false
50
48
  Option('foo').empty? == false
51
49
  Option('foo').truly? == true
52
50
 
@@ -68,6 +66,8 @@ Treat it like an array:
68
66
  Falsey values (kind-of) examples:
69
67
 
70
68
  user = Option(User.find(123))
69
+ user.name._
70
+
71
71
  user.value('You are not logged in') { |user| "You are logged in as #{user.name}" }.should == 'You are logged in as foo'
72
72
 
73
73
  if user != nil
@@ -80,7 +80,7 @@ Falsey values (kind-of) examples:
80
80
  user.subscribed?.value(false) # same as above
81
81
  user.subscribed?.or(false) # same as above
82
82
 
83
- Remember! an Option is never false, if you want to know if it is false, call `#none?` of `#truly?`
83
+ Remember! an Option is never false (in Ruby terms), if you want to know if it is false, call `#empty?` of `#truly?`
84
84
 
85
85
  `#truly?` will return true or false, always.
86
86
 
@@ -105,8 +105,34 @@ Slug example
105
105
  end
106
106
 
107
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/)
108
+ see also
109
+
110
+ * [Monad equivalend in Ruby](http://stackoverflow.com/questions/2709361/monad-equivalent-in-ruby)
111
+ * [Option Type ](http://devblog.avdi.org/2011/05/30/null-objects-and-falsiness/)
112
+ * [NullObject and Falsiness by @avdi](http://devblog.avdi.org/2011/05/30/null-objects-and-falsiness/)
113
+ * [andand](https://github.com/raganwald/andand/blob/master/README.textile)
114
+ * [ick](http://ick.rubyforge.org/)
115
+ * [Monads in Ruby](http://moonbase.rydia.net/mental/writings/programming/monads-in-ruby/00introduction.html)
116
+ * [The Maybe Monad in Ruby](http://pretheory.wordpress.com/2008/02/14/the-maybe-monad-in-ruby/)
117
+ * [Monads in Ruby with nice syntax](http://www.valuedlessons.com/2008/01/monads-in-ruby-with-nice-syntax.html)
118
+ * [Maybe in Ruby](https://github.com/bhb/maybe)
119
+ * [Monads on the Cheap](http://osteele.com/archives/2007/12/cheap-monads)
120
+ * [Rumonade](https://github.com/ms-ati/rumonade)
121
+ * [Monads for functional programming](http://homepages.inf.ed.ac.uk/wadler/papers/marktoberdorf/baastad.pdf)
122
+
123
+ ## Installation
124
+
125
+ Add this line to your application's Gemfile:
126
+
127
+ gem 'monadic'
128
+
129
+ And then execute:
130
+
131
+ $ bundle
132
+
133
+ Or install it yourself as:
134
+
135
+ $ gem install monadic
110
136
 
111
137
  ## Contributing
112
138
 
@@ -1,89 +1,93 @@
1
1
  require 'singleton'
2
+ # Represents optional values. Instances of Option are either an instance of Some or the object None.
3
+ #
2
4
 
5
+ # Helper function which returns Some or None respectively, depending on their value
6
+ # I find this moar simplistic in ruby than the traditional #bind and #unit
3
7
  def Option(value)
4
8
  return None if value.nil? || (value.respond_to?(:empty?) && value.empty?)
5
9
  return Some.new(value)
6
10
  end
7
- alias :Some :Option
11
+ alias :Some :Option
12
+ alias :Maybe :Option
8
13
 
9
- class Some
14
+ # Represents the Option if there is some value available
15
+ class Some
10
16
  def initialize(value)
11
17
  @value = value
12
18
  end
13
19
 
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
20
  def to_ary
29
21
  return [@value].flatten if @value.respond_to? :flatten
30
22
  return [@value]
31
23
  end
32
24
  alias :to_a :to_ary
33
25
 
34
- def map(&block)
35
- return Option(@value.map(&block)) if @value.is_a?(Enumerable)
36
- return Option(block.call)
26
+ def empty?
27
+ false
37
28
  end
38
29
 
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
30
+ def truly?
31
+ @value == true
43
32
  end
44
33
 
45
34
  def value(default=None, &block)
46
- return default if none?
35
+ return default if empty?
47
36
  return block.call(@value) if block_given?
48
37
  return @value
49
- end
38
+ end
50
39
  alias :or :value
51
40
  alias :_ :value
52
41
 
42
+ def map(func = nil, &block)
43
+ return Option(@value.map(&block)) if @value.is_a?(Enumerable)
44
+ return Option((func || block).call(@value))
45
+ end
46
+
53
47
  def method_missing(m, *args)
54
48
  Option(@value.__send__(m, *args))
55
- end
49
+ end
50
+
51
+ def select(func = nil, &block)
52
+ return Option(@value.select(&block)) if @value.is_a?(Enumerable)
53
+ return None unless (func || block).call(@value)
54
+ return self
55
+ end
56
56
 
57
57
  def ==(other)
58
58
  return false unless other.is_a? Some
59
59
  @value == other.instance_variable_get(:@value)
60
- end
60
+ end
61
+
62
+ def to_s
63
+ "Some(#{@value.to_s})"
64
+ end
61
65
  end
62
66
 
67
+ # Represents the Option if there is no value available
63
68
  class None
64
69
  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
70
  def to_ary
76
71
  []
77
72
  end
78
73
  alias :to_a :to_ary
79
74
 
80
- def none?
75
+ def empty?
81
76
  true
82
77
  end
83
- alias :empty? :none?
78
+
79
+ def method_missing(m, *args)
80
+ self
81
+ end
84
82
 
85
83
  def truly?
86
84
  false
87
85
  end
86
+
87
+ def value(default=self)
88
+ default
89
+ end
90
+ alias :or :value
91
+ alias :_ :value
88
92
  end
89
93
  end
@@ -1,3 +1,3 @@
1
1
  module Monadic
2
- VERSION = "0.0.2"
2
+ VERSION = "0.0.3"
3
3
  end
data/monadic.gemspec CHANGED
@@ -20,4 +20,5 @@ Gem::Specification.new do |gem|
20
20
  gem.add_development_dependency 'guard-rspec'
21
21
  gem.add_development_dependency 'guard-bundler'
22
22
  gem.add_development_dependency 'growl'
23
+ gem.add_development_dependency 'activesupport'
23
24
  end
data/spec/option_spec.rb CHANGED
@@ -7,22 +7,29 @@ describe 'Option' do
7
7
 
8
8
  it 'None stays None' do
9
9
  Option(nil)._.should == None
10
- Option(nil).none?.should be_true
11
10
  Option(nil).empty?.should be_true
12
11
  end
13
12
 
14
13
  it 'Some stays Some' do
15
14
  Option('foo').should be_kind_of(Some)
16
- Option('foo').none?.should be_false
17
15
  Option('foo').empty?.should be_false
18
16
  end
19
17
 
20
- it 'None to_s is "None"' do
18
+ it 'Some#to_s is "Some(value)"' do
19
+ Some(123).to_s.should == "Some(123)"
20
+ end
21
+
22
+ it 'None#to_s is "None"' do
21
23
  option = Option(nil)
22
24
  "#{option}".should == "None"
23
25
  "#{option._}".should == "None"
24
26
  end
25
27
 
28
+ it 'None is always empty' do
29
+ None.empty?.should be_true
30
+ Maybe(nil).empty?.should be_true
31
+ end
32
+
26
33
  it '[] as value always returns None()' do
27
34
  Option([]).a.should == None
28
35
  end
@@ -56,11 +63,6 @@ describe 'Option' do
56
63
  Option(nil).should_not be_false
57
64
  end
58
65
 
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
66
  class User
65
67
  attr_reader :name
66
68
  def initialize(name)
@@ -105,4 +107,25 @@ describe 'Option' do
105
107
  Option(nil).to_a.should == []
106
108
  end
107
109
 
110
+ it 'diving into hashes' do
111
+ Maybe({})['a']['b']['c'].should == None
112
+ Maybe({a: 1})[:a]._.should == 1
113
+ end
114
+
115
+ it 'should support Rumonades example' do
116
+ require 'active_support/time'
117
+ def format_date_in_march(time_or_date_or_nil)
118
+ Option(time_or_date_or_nil). # wraps possibly-nil value in an Option monad (Some or None)
119
+ map(&:to_date). # transforms a contained Time value into a Date value
120
+ select {|d| d.month == 3}. # filters out non-matching Date values (Some becomes None)
121
+ map(&:to_s). # transforms a contained Date value into a String value
122
+ map {|s| s.gsub('-', '')}. # transforms a contained String value by removing '-'
123
+ value("not in march!") # returns the contained value, or the alternative if None
124
+ end
125
+
126
+ format_date_in_march(nil).should == "not in march!"
127
+ format_date_in_march(Time.parse('2009-01-01 01:02')).should == "not in march!"
128
+ format_date_in_march(Time.parse('2011-03-21 12:34')).should == "20110321"
129
+ end
130
+
108
131
  end
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.2
4
+ version: 0.0.3
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-04-29 00:00:00.000000000 Z
12
+ date: 2012-04-30 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rspec
@@ -91,6 +91,22 @@ dependencies:
91
91
  - - ! '>='
92
92
  - !ruby/object:Gem::Version
93
93
  version: '0'
94
+ - !ruby/object:Gem::Dependency
95
+ name: activesupport
96
+ requirement: !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ! '>='
100
+ - !ruby/object:Gem::Version
101
+ version: '0'
102
+ type: :development
103
+ prerelease: false
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ! '>='
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
94
110
  description: brings some functional goodness to ruby
95
111
  email:
96
112
  - pz@anixe.pl
@@ -100,6 +116,7 @@ extra_rdoc_files: []
100
116
  files:
101
117
  - .gitignore
102
118
  - .rspec
119
+ - CHANGELOG.md
103
120
  - Gemfile
104
121
  - Guardfile
105
122
  - LICENSE