tweed 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/README ADDED
@@ -0,0 +1,49 @@
1
+ <!--*-markdown-*-->
2
+
3
+ # Tweed
4
+
5
+ Tweed is a small pure-Ruby library for implementing [monads][].
6
+
7
+ [monads]: http://en.wikipedia.org/wiki/Monad_%28functional_programming%29
8
+
9
+ ## Examples
10
+
11
+ require 'tweed'
12
+
13
+ ### The Maybe Monad
14
+
15
+ Implementing [the Maybe monad][]:
16
+
17
+ [the maybe monad]: http://en.wikipedia.org/wiki/Monad_%28functional_programming%29#Maybe_monad
18
+
19
+ class Maybe < Tweed::Monad
20
+ define_monad do
21
+ construct { |value, success| @value, @success = value, success }
22
+ unit { |value| self.new(value, true) }
23
+ bind { |&block| @success ? block.call(@value) : self }
24
+ zero { self.new(nil, false) }
25
+ end
26
+ end
27
+
28
+ Using the Maybe monad to implement safe division:
29
+
30
+ module SafeDivision
31
+ def /(other)
32
+ self.bind do |x|
33
+ other.bind do |y|
34
+ y.zero? ? self.class.zero : self.class[x / y]
35
+ end
36
+ end
37
+ end
38
+ end
39
+
40
+ And then using the `SafeDivision` module:
41
+
42
+ class X < Maybe
43
+ include SafeDivision
44
+ end
45
+
46
+ success = X[1] / X[2.0] # => X[0.5]
47
+ failure = X[1] / X[0] # => X.zero
48
+
49
+ This will *not* result in a zero division error.
@@ -0,0 +1,24 @@
1
+ This is free and unencumbered software released into the public domain.
2
+
3
+ Anyone is free to copy, modify, publish, use, compile, sell, or
4
+ distribute this software, either in source code form or as a compiled
5
+ binary, for any purpose, commercial or non-commercial, and by any
6
+ means.
7
+
8
+ In jurisdictions that recognize copyright laws, the author or authors
9
+ of this software dedicate any and all copyright interest in the
10
+ software to the public domain. We make this dedication for the benefit
11
+ of the public at large and to the detriment of our heirs and
12
+ successors. We intend this dedication to be an overt act of
13
+ relinquishment in perpetuity of all present and future rights to this
14
+ software under copyright law.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19
+ IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
20
+ OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
21
+ ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22
+ OTHER DEALINGS IN THE SOFTWARE.
23
+
24
+ For more information, please refer to <http://unlicense.org/>
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1,10 @@
1
+ class Object
2
+ def singleton_class
3
+ class << self; self; end
4
+ end
5
+ end
6
+
7
+ module Tweed
8
+ autoload :Monad, 'tweed/monad'
9
+ autoload :Monads, 'tweed/monads'
10
+ end
@@ -0,0 +1,156 @@
1
+ module Tweed
2
+ ##
3
+ # Abstract superclass to represent monads.
4
+ #
5
+ # To create a monad, subclass and define the monad’s Kleisli triple using
6
+ # {Monad.define_monad define_monad}.
7
+ #
8
+ # @example Implementing the Maybe monad
9
+ # class Maybe < Tweed::Monad
10
+ # define_monad do
11
+ # # This becomes the `initialize` instance method:
12
+ # construct { |value, success| @value, @success = value, success }
13
+ #
14
+ # # This becomes the `return` class method:
15
+ # unit { |value| self.new(value, true) }
16
+ #
17
+ # # This becomes the `bind` instance method:
18
+ # bind { |&block| @success ? block.call(@value) : self }
19
+ #
20
+ # # This becomes the (cached) `zero` class method:
21
+ # zero { self.new(nil, false) }
22
+ # end
23
+ #
24
+ # def inspect
25
+ # @success ? "Just #{@value.inspect}" : "Nothing"
26
+ # end
27
+ # end
28
+ #
29
+ # @example Using the Maybe monad to implement safe division
30
+ # class SafeDivision < Maybe
31
+ # def /(other)
32
+ # self.bind do |x|
33
+ # other.bind do |y|
34
+ # y.zero? ? self.class.zero : self.class[x / y]
35
+ # end
36
+ # end
37
+ # end
38
+ # end
39
+ #
40
+ # x = SafeDivision[1] / SafeDivision[2.0] # => Just 0.5
41
+ # y = SafeDivision[1] / SafeDivision[0] # => Nothing
42
+ #
43
+ class Monad
44
+
45
+ # Lift a value into this monad.
46
+ # @abstract Subclass and override using {Monad.define_monad define_monad}.
47
+ # @return [Monad] the newly created monad.
48
+ def self.return(*args)
49
+ abstract
50
+ end
51
+
52
+ # Pull an underlying value out of this monad.
53
+ # @abstract Subclass and override using {Monad.define_monad define_monad}.
54
+ def bind(&block)
55
+ abstract
56
+ end
57
+
58
+ # Syntactic sugar for {Monad.return Monad.return(...)}.
59
+ # @return [Monad]
60
+ def self.[](*args)
61
+ self.return(*args)
62
+ end
63
+
64
+ ##
65
+ # Adaptation of `liftM2` from Haskell. ‘Lifts’ a binary function into the
66
+ # current monad.
67
+ #
68
+ # @example Add two numbers
69
+ # m_x = SomeMonad[1]
70
+ # m_y = SomeMonad[2]
71
+ # m_z = m_x.lift_m2(m_y) { |x, y| x + y } # => SomeMonad[1 + 2] => SomeMonad[3]
72
+ #
73
+ # @param other another value within the current monad.
74
+ # @yield [x, y] the underlying values of `self` and `other`.
75
+ # @return the result of the binary operation, lifted back into the current monad.
76
+ def lift_m2(other)
77
+ self.bind do |x|
78
+ other.bind do |y|
79
+ self.class[yield(x, y)]
80
+ end
81
+ end
82
+ end
83
+
84
+ protected
85
+
86
+ ##
87
+ # Class method to set up a Monad subclass using a Kleisli triple.
88
+ #
89
+ # Uses a custom DSL to define the triple and an optional monadic zero.
90
+ #
91
+ # @example Implementing the List monad
92
+ # class List < Tweed::Monad
93
+ # define_monad do
94
+ # # This becomes the `initialize` instance method, so instance
95
+ # # variables will be set on the Monad instance.
96
+ # construct { |sequence| @sequence = sequence }
97
+ #
98
+ # # This becomes the `return` class method, also accessible via `[]`
99
+ # # on both the class and instances.
100
+ # unit { |value| self.new([value]) }
101
+ #
102
+ # # This becomes the `bind` instance method, and will always receive
103
+ # # a block; it should explicitly use `block.call` instead of `yield`
104
+ # # due to the way blocks work in Ruby (Ruby will complain that no
105
+ # # block was passed if you `yield`).
106
+ # bind { |&block| self.new(@sequence.map(&block).inject(&:+)) }
107
+ #
108
+ # # The definition of zero for this monad. This will be called once
109
+ # # from the class and have its result cached.
110
+ # zero { self.new([].freeze!) }
111
+ # end
112
+ # end
113
+ #
114
+ # @see http://en.wikipedia.org/wiki/Monad_(functional_programming)#Concepts
115
+ # Wikipedia article on Monads
116
+ def self.define_monad(&def_block)
117
+ # Capture the Kleisli triple, and perhaps a monadic zero.
118
+ capture = Object.new
119
+
120
+ class << capture
121
+ def construct(&block) @construct = block end
122
+ def unit(&block) @unit = block end
123
+ def bind(&block) @bind = block end
124
+ def zero(&block) @zero = block end
125
+ end
126
+
127
+ capture.instance_eval(&def_block)
128
+
129
+ class << capture
130
+ def [](sym) instance_variable_get("@#{sym}".to_sym) end
131
+ end
132
+
133
+ # Define the necessary methods on the monad, given the components of the
134
+ # Kleisli triple (and optionally the monadic zero).
135
+ self.class_eval do
136
+ define_method(:initialize, &capture[:construct]) unless capture[:construct].nil?
137
+ define_method(:bind, &capture[:bind]) unless capture[:bind].nil?
138
+ self.singleton_class.instance_eval do
139
+ define_method(:return, &capture[:unit]) unless capture[:unit].nil?
140
+ unless capture[:zero].nil?
141
+ define_method(:get_zero, &capture[:zero])
142
+ private(:get_zero)
143
+ define_method(:zero) { @zero ||= get_zero }
144
+ end
145
+ end
146
+ end
147
+
148
+ self
149
+ end # def self.define_monad
150
+ end # class Monad
151
+ end # module Tweed
152
+
153
+ # @private
154
+ def abstract
155
+ raise NotImplementedError
156
+ end
@@ -0,0 +1,7 @@
1
+ module Tweed
2
+ module Monads
3
+ autoload :Identity, 'tweed/monads/identity'
4
+ autoload :List, 'tweed/monads/list'
5
+ autoload :Maybe, 'tweed/monads/maybe'
6
+ end
7
+ end
@@ -0,0 +1,23 @@
1
+ module Tweed
2
+ module Monads
3
+ class Identity < Tweed::Monad
4
+ define_monad do
5
+ construct { |value| @value = value }
6
+ unit { |value| self.new(value) }
7
+ bind { |&block| block.call(@value) }
8
+ end
9
+
10
+ def inspect
11
+ "#{self.class.inspect}[#{@value.inspect}]"
12
+ end
13
+
14
+ def ==(other)
15
+ if other.is_a? Identity
16
+ @value == other.instance_eval { @value }
17
+ else
18
+ @value == other
19
+ end
20
+ end
21
+ end # class Identity
22
+ end # module Monads
23
+ end # module Tweed
@@ -0,0 +1,32 @@
1
+ module Tweed
2
+ module Monads
3
+ class List < Tweed::Monad
4
+ define_monad do
5
+ construct { |sequence| @sequence = sequence }
6
+ unit { |value| self.new([value]) }
7
+ bind do |&block|
8
+ self.class.new(@sequence.map do |item|
9
+ block.call(item).to_a
10
+ end.inject(&:+))
11
+ end
12
+ zero { self.new([].freeze!) }
13
+ end
14
+
15
+ def +(other)
16
+ self.class.new(self.to_a + other.to_a)
17
+ end
18
+
19
+ def inspect
20
+ "#{self.class.inspect} :: #{@sequence.inspect}"
21
+ end
22
+
23
+ def to_a
24
+ @sequence.to_a
25
+ end
26
+
27
+ def ==(other)
28
+ self.to_a == other.to_a
29
+ end
30
+ end # class List
31
+ end # module Monads
32
+ end # module Tweed
@@ -0,0 +1,37 @@
1
+ module Tweed
2
+ module Monads
3
+ ## The Maybe monad
4
+ class Maybe < Tweed::Monad
5
+ define_monad do
6
+ construct { |value, success| @value, @success = value, success }
7
+ unit { |value| self.new(value, true) }
8
+ bind { |&block| @success ? block.call(@value) : self }
9
+ zero { self.new(nil, false) }
10
+ end
11
+
12
+ def inspect
13
+ repr = @success ? "[#{@value.inspect}]" : ".zero"
14
+ "#{self.class.inspect}#{repr}"
15
+ end
16
+
17
+ def ==(other)
18
+ if other.is_a? Maybe
19
+ [@value, @success] == other.instance_eval { [@value, @success] }
20
+ else
21
+ self.object_id == other.object_id || @value == other
22
+ end
23
+ end
24
+
25
+ # An example of safe division implemented over the Maybe monad.
26
+ module SafeDivision
27
+ def /(other)
28
+ self.bind do |x|
29
+ other.bind do |y|
30
+ y.zero? ? self.class.zero : self.class[x / y]
31
+ end
32
+ end
33
+ end # def /
34
+ end # module SafeDivision
35
+ end # class Maybe
36
+ end # module Monads
37
+ end # module Tweed
metadata ADDED
@@ -0,0 +1,82 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: tweed
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Zachary Voase
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2010-01-14 00:00:00 +00:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: yard
17
+ type: :development
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 0.5.2
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: bluecloth
27
+ type: :development
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 2.0.5
34
+ version:
35
+ description: Tweed is an implementation of monads in Ruby.
36
+ email: zacharyvoase@me.com
37
+ executables: []
38
+
39
+ extensions: []
40
+
41
+ extra_rdoc_files: []
42
+
43
+ files:
44
+ - README
45
+ - UNLICENSE
46
+ - VERSION
47
+ - lib/tweed/monad.rb
48
+ - lib/tweed/monads/identity.rb
49
+ - lib/tweed/monads/list.rb
50
+ - lib/tweed/monads/maybe.rb
51
+ - lib/tweed/monads.rb
52
+ - lib/tweed.rb
53
+ has_rdoc: true
54
+ homepage: http://bitbucket.org/zacharyvoase/tweed
55
+ licenses:
56
+ - Public Domain
57
+ post_install_message:
58
+ rdoc_options: []
59
+
60
+ require_paths:
61
+ - lib
62
+ required_ruby_version: !ruby/object:Gem::Requirement
63
+ requirements:
64
+ - - ">="
65
+ - !ruby/object:Gem::Version
66
+ version: "0"
67
+ version:
68
+ required_rubygems_version: !ruby/object:Gem::Requirement
69
+ requirements:
70
+ - - ">="
71
+ - !ruby/object:Gem::Version
72
+ version: "0"
73
+ version:
74
+ requirements: []
75
+
76
+ rubyforge_project:
77
+ rubygems_version: 1.3.5
78
+ signing_key:
79
+ specification_version: 3
80
+ summary: An implementation of monads in Ruby.
81
+ test_files: []
82
+