obfusk 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: b19543e886d856d83dffbbf56a1d56a906d12afa
4
+ data.tar.gz: b00658481e756a75ec2aa009871fab55aac0c6fd
5
+ SHA512:
6
+ metadata.gz: 44b10eb7fede5ebbadeba7f508b93a24ed01740dadbd2d81a353960859f1f7b4f8ec2928f319e18567ac3a8fec4220f4e9e8e5729359be5437d9250952e86a5e
7
+ data.tar.gz: 139559e07e39770ca5d89da192acfd843723b1789f2bbf01e054361a31df3a773b0ed0f2a4acbb8b4eb4a4ea7e3296edd8863633b814715cd839ab0289a3b142
data/.yardopts ADDED
@@ -0,0 +1 @@
1
+ --markup markdown
data/README.md ADDED
@@ -0,0 +1,123 @@
1
+ []: {{{1
2
+
3
+ File : README.md
4
+ Maintainer : Felix C. Stegerman <flx@obfusk.net>
5
+ Date : 2014-06-16
6
+
7
+ Copyright : Copyright (C) 2014 Felix C. Stegerman
8
+ Version : v0.1.0
9
+
10
+ []: }}}1
11
+
12
+ [![Gem Version](https://badge.fury.io/rb/obfusk.png)](https://badge.fury.io/rb/obfusk)
13
+
14
+ ## Description
15
+
16
+ obfusk.rb - functional programming library for ruby
17
+
18
+ ## Examples
19
+ []: {{{1
20
+
21
+ ```ruby
22
+ require 'obfusk/adt'
23
+
24
+ class Foo
25
+ include Obfusk::ADT
26
+ constructor :Bar
27
+ constructor :Baz, :value
28
+ end
29
+
30
+ x = Foo.Bar()
31
+ y = Foo.Baz 99
32
+
33
+ puts y.value # => 99
34
+ ```
35
+
36
+ ```ruby
37
+ require 'obfusk/lazy'
38
+
39
+ x = Obfusk.lazy { some_expensive_computation_that_returns_42 }
40
+ x._ # => 42 (expensive computation not run until now)
41
+ x._ # => 42 (cached)
42
+
43
+ y = Obfusk.lazy { Foo.new 42 }
44
+ z = y.chain(:value)
45
+ z._ # => 42 (.new and .value not run until now)
46
+
47
+ Obfusk.eager(lazy_or_not) # => value
48
+ ```
49
+
50
+ ```ruby
51
+ require 'obfusk/list'
52
+
53
+ fibs = Obfusk.List(0,1) { fibs.zipWith(fibs.tail, &:+) }
54
+
55
+ fibs == Obfusk.Nil
56
+ # => false
57
+
58
+ fibs.take(10).to_a
59
+ # => [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
60
+
61
+ fibs.map { |x| x*x } .take(10).to_a
62
+ # => [0, 1, 1, 4, 9, 25, 64, 169, 441, 1156]
63
+ ```
64
+
65
+ ```ruby
66
+ require 'obfusk/monad'
67
+
68
+ class Foo
69
+ include Obfusk::Monad
70
+ def self.return(x)
71
+ # ...
72
+ end
73
+ def self.bind_pass(m, &b)
74
+ # ...
75
+ end
76
+ end
77
+
78
+ f = -> x { '...' }
79
+ g = -> y { '...' }
80
+
81
+ Foo.pipeline Foo.new('...'), f, g
82
+ ```
83
+
84
+ ```ruby
85
+ require 'obfusk/monads'
86
+
87
+ x = Obfusk.Nothing
88
+ y = Obfusk.Just 42
89
+
90
+ x.bind(y)
91
+ # => Obfusk.Nothing
92
+
93
+ Obfusk.Left "oops"
94
+ Obfusk.Right 37
95
+ ```
96
+
97
+ ...
98
+
99
+ []: }}}1
100
+
101
+ ## Specs & Docs
102
+
103
+ ```bash
104
+ $ rake spec
105
+ $ rake docs
106
+ ```
107
+
108
+ ## TODO
109
+
110
+ * more lists operations
111
+ * more stuff from obfusk.coffee
112
+ * ...
113
+
114
+ ## License
115
+
116
+ LGPLv3+ [1].
117
+
118
+ ## References
119
+
120
+ [1] GNU Lesser General Public License, version 3
121
+ --- http://www.gnu.org/licenses/lgpl-3.0.html
122
+
123
+ []: ! ( vim: set tw=70 sw=2 sts=2 et fdm=marker : )
data/Rakefile ADDED
@@ -0,0 +1,58 @@
1
+ desc 'Run specs'
2
+ task :spec do
3
+ sh 'rspec -c'
4
+ end
5
+
6
+ desc 'Run specs verbosely'
7
+ task 'spec:verbose' do
8
+ sh 'rspec -cfd'
9
+ end
10
+
11
+ desc 'Run specs verbosely, view w/ less'
12
+ task 'spec:less' do
13
+ sh 'rspec -cfd --tty | less -R'
14
+ end
15
+
16
+ desc 'Check for warnings'
17
+ task :warn do
18
+ sh 'ruby -w -I lib -r obfusk -e ""' # TODO
19
+ end
20
+
21
+ desc 'Check for warnings in specs'
22
+ task 'warn:spec' do
23
+ reqs = Dir['spec/**/*.rb'].sort.map { |x| "-r ./#{x}" } * ' '
24
+ sh "ruby -w -I lib -r rspec #{reqs} -e ''"
25
+ end
26
+
27
+ desc 'Check for warnings in specs (but not void context)'
28
+ task 'warn:spec:novoid' do
29
+ sh 'rake warn:spec 2>&1 | grep -v "void context"'
30
+ end
31
+
32
+ desc 'Generate docs'
33
+ task :docs do
34
+ sh 'yardoc | cat'
35
+ end
36
+
37
+ desc 'List undocumented objects'
38
+ task 'docs:undoc' do
39
+ sh 'yard stats --list-undoc'
40
+ end
41
+
42
+ desc 'Cleanup'
43
+ task :clean do
44
+ sh 'rm -rf .yardoc/ doc/ *.gem'
45
+ end
46
+
47
+ desc 'Build SNAPSHOT gem'
48
+ task :snapshot do
49
+ v = Time.new.strftime '%Y%m%d%H%M%S'
50
+ f = 'lib/obfusk/version.rb'
51
+ sh "sed -ri~ 's!(SNAPSHOT)!\\1.#{v}!' #{f}"
52
+ sh 'gem build obfusk.gemspec'
53
+ end
54
+
55
+ desc 'Undo SNAPSHOT gem'
56
+ task 'snapshot:undo' do
57
+ sh 'git checkout -- lib/obfusk/version.rb'
58
+ end
data/lib/obfusk/adt.rb ADDED
@@ -0,0 +1,172 @@
1
+ # -- ; {{{1
2
+ #
3
+ # File : obfusk/adt.rb
4
+ # Maintainer : Felix C. Stegerman <flx@obfusk.net>
5
+ # Date : 2014-06-15
6
+ #
7
+ # Copyright : Copyright (C) 2014 Felix C. Stegerman
8
+ # Licence : LGPLv3+
9
+ #
10
+ # -- ; }}}1
11
+
12
+ require 'thread'
13
+
14
+ module Obfusk
15
+
16
+ ADT_Meta__ = { inheriting: [false], mutex: Mutex.new }
17
+
18
+ # Algebraic Data Type
19
+ module ADT
20
+ include Comparable
21
+
22
+ def self.included(base)
23
+ base.extend ClassMethods
24
+ end
25
+
26
+ # use a contructor!
27
+ # @raise NoMethodError
28
+ def initialize
29
+ raise NoMethodError, 'use a contructor!' # TODO
30
+ end
31
+
32
+ module ClassMethods
33
+ # duplicate constructors for subclasses
34
+ def inherited(subclass)
35
+ return if ::Obfusk::ADT_Meta__[:inheriting].last
36
+ ctors = constructors
37
+ subclass.class_eval do
38
+ ctors.each_pair do |k,v|
39
+ constructor v[:ctor_name], *v[:ctor_keys], &v[:ctor_block]
40
+ end
41
+ end
42
+ end
43
+
44
+ # create a constructor
45
+ # @param [Symbol] name the name of the constructor
46
+ # @param [<Symbol>] keys the keys of the constructor
47
+ def constructor(name, *keys, &b)
48
+ keys_ = keys.map(&:to_sym)
49
+ name_ = name.to_sym
50
+ ctor = ::Obfusk::ADT_Meta__[:mutex].synchronize do
51
+ begin
52
+ ::Obfusk::ADT_Meta__[:inheriting] << true
53
+ Class.new self
54
+ ensure
55
+ ::Obfusk::ADT_Meta__[:inheriting].pop
56
+ end
57
+ end
58
+ ctor.class_eval do
59
+ attr_accessor :ctor, :ctor_name, :ctor_keys
60
+ keys_.each { |k| define_method(k) { @data[k] } }
61
+ define_method(:initialize) do |guard, ctor, *values, &f|
62
+ raise ArgumentError, 'for internal use only!' \
63
+ unless guard == :for_internal_use_only
64
+ if !b && (k = keys_.length) != (v = values.length)
65
+ raise ArgumentError, "wrong number of arguments (#{v} for #{k})"
66
+ end
67
+ data = Hash[keys_.zip values]
68
+ @ctor = ctor ; @ctor_name = name_ ; @ctor_keys = keys_
69
+ @data = b ? b[self, data, values, f] : data
70
+ end
71
+ end
72
+ class_eval do
73
+ const_set name_, ctor
74
+ f = -> v, b { ctor.new :for_internal_use_only, ctor, *v, &b }
75
+ if !b && keys.empty?
76
+ singleton = f[[],nil]
77
+ define_singleton_method(name_) { singleton }
78
+ define_method(name_) { singleton }
79
+ else
80
+ define_singleton_method(name_) { |*values,&b| f[values,b] }
81
+ define_method(name_) { |*values,&b| f[values,b] }
82
+ end
83
+ constructors[name_] = {
84
+ ctor: ctor, method: method(name_),
85
+ ctor_name: name_, ctor_keys: keys_, ctor_block: b
86
+ }
87
+ end
88
+ name_
89
+ end
90
+ private :constructor
91
+
92
+ # the constructors
93
+ def constructors
94
+ @constructors ||= {}
95
+ end
96
+
97
+ # import the constructors into another namespace
98
+ def import_constructors(scope)
99
+ constructors.each_pair do |k,v|
100
+ m = method k
101
+ scope.define_singleton_method(k) { |*a,&b| m[*a,&b] }
102
+ scope.const_set k, v[:ctor]
103
+ end
104
+ end
105
+
106
+ # pattern matching
107
+ def match(x, opts)
108
+ raise ArgumentError, 'not an ADT' unless x.is_a?(::Obfusk::ADT)
109
+ raise ArgumentError,
110
+ "types do not match (#{x.class.superclass} for #{self})" \
111
+ unless x.class.superclass == self
112
+ x.match opts
113
+ end
114
+ end
115
+
116
+ # the data
117
+ def _data
118
+ @data
119
+ end
120
+
121
+ # equal?
122
+ def ==(rhs)
123
+ rhs.is_a?(::Obfusk::ADT) &&
124
+ self.class.superclass == rhs.class.superclass &&
125
+ ctor == rhs.ctor && _eq_data(rhs)
126
+ end
127
+
128
+ # equal and of the same type?
129
+ def eql?(rhs)
130
+ self == rhs
131
+ end
132
+
133
+ # ordering
134
+ def <=>(rhs)
135
+ return nil unless rhs.is_a?(::Obfusk::ADT) &&
136
+ self.class.superclass == rhs.class.superclass
137
+ k = self.class.superclass.constructors.keys
138
+ ctor != rhs.ctor ? k.index(ctor_name) <=> k.index(rhs.ctor_name) :
139
+ _compare_data(rhs)
140
+ end
141
+
142
+ def _eq_data(rhs)
143
+ _data == rhs._data
144
+ end
145
+
146
+ def _compare_data(rhs)
147
+ _data.values_at(*ctor_keys) <=> rhs._data.values_at(*ctor_keys)
148
+ end
149
+
150
+ # pattern matching
151
+ def match(opts)
152
+ unless (ck = self.class.superclass.constructors.keys.sort) ==
153
+ (ok = opts.keys.sort)
154
+ raise ArgumentError,
155
+ "constructors do not match (#{ok} for #{ck})"
156
+ end
157
+ opts[ctor_name][self]
158
+ end
159
+
160
+ # to string
161
+ def to_s
162
+ "#<#{self.class.superclass.name || '#ADT'}.#{ctor_name}: #{@data}>"
163
+ end
164
+
165
+ # to string
166
+ def inspect
167
+ to_s
168
+ end
169
+ end
170
+ end
171
+
172
+ # vim: set tw=70 sw=2 sts=2 et fdm=marker :
@@ -0,0 +1,37 @@
1
+ # -- ; {{{1
2
+ #
3
+ # File : obfusk/lazy.rb
4
+ # Maintainer : Felix C. Stegerman <flx@obfusk.net>
5
+ # Date : 2014-06-15
6
+ #
7
+ # Copyright : Copyright (C) 2014 Felix C. Stegerman
8
+ # Licence : LGPLv3+
9
+ #
10
+ # -- ; }}}1
11
+
12
+ module Obfusk
13
+ # lazy evaluation (thunk)
14
+ def self.lazy(x = nil, &b)
15
+ return x if lazy? x
16
+ f = b ? b : -> { x }; v = nil; e = false
17
+ g = -> () { unless e then v = f[]; e = true end; v }
18
+ g.define_singleton_method(:__obfusk_lazy__?) { true }
19
+ g.define_singleton_method(:_) { g[] }
20
+ g.define_singleton_method(:chain) do |m,*a,&b|
21
+ ::Obfusk::lazy { g[].public_send(m,*a,&b) }
22
+ end
23
+ g
24
+ end
25
+
26
+ # lazy?
27
+ def self.lazy?(x)
28
+ x.respond_to?(:__obfusk_lazy__?) && x.__obfusk_lazy__?
29
+ end
30
+
31
+ # eager: evaluate if lazy, just return if not
32
+ def self.eager(x)
33
+ lazy?(x) ? x._ : x
34
+ end
35
+ end
36
+
37
+ # vim: set tw=70 sw=2 sts=2 et fdm=marker :
@@ -0,0 +1,256 @@
1
+ # -- ; {{{1
2
+ #
3
+ # File : obfusk/list.rb
4
+ # Maintainer : Felix C. Stegerman <flx@obfusk.net>
5
+ # Date : 2014-06-15
6
+ #
7
+ # Copyright : Copyright (C) 2014 Felix C. Stegerman
8
+ # Licence : LGPLv3+
9
+ #
10
+ # -- ; }}}1
11
+
12
+ require 'obfusk/adt'
13
+ require 'obfusk/lazy'
14
+ require 'obfusk/monad'
15
+
16
+ module Obfusk
17
+ # Lazy List
18
+ class List
19
+ include ADT
20
+ include Monad
21
+ include MonadPlus
22
+
23
+ constructor :Nil
24
+ constructor(:Cons, :head, :tail) do |cls, data, values, f|
25
+ v = values.length + (f ? 1 : 0)
26
+ if (k = cls.ctor_keys.length) != v
27
+ raise ArgumentError, "wrong number of arguments (#{v} for #{k})"
28
+ end
29
+ { head: data[:head], tail: f ? ::Obfusk.lazy(&f) :
30
+ ::Obfusk.lazy(data[:tail]) }
31
+ end
32
+
33
+ class Cons
34
+ # lazy tail
35
+ alias :lazy_tail :tail
36
+
37
+ # eager tail
38
+ def tail
39
+ lazy_tail._
40
+ end
41
+ end
42
+
43
+ def _eq_data(rhs)
44
+ match Nil: -> (_) { true },
45
+ Cons: -> (_) { [head,tail] == [rhs.head,rhs.tail] }
46
+ end
47
+
48
+ def _compare_data(rhs)
49
+ match Nil: -> (_) { 0 },
50
+ Cons: -> (_) { [head,tail] <=> [rhs.head,rhs.tail] }
51
+ end
52
+
53
+ # --
54
+
55
+ def each(&b)
56
+ return enum_for :each unless b
57
+ xs = self; while xs != Nil() do b[xs.head]; xs = xs.tail end
58
+ end
59
+
60
+ def to_s
61
+ *xs, x = self.class.name.split '::'; n = xs*'::' + '.' + x
62
+ self == Nil() ? "<##{n}>" : "<##{n}(#{head},...)>"
63
+ end
64
+
65
+ def to_a
66
+ each.to_a
67
+ end
68
+
69
+ # pretend to be lazy (so we don't need Obfusk.eager)
70
+ def _
71
+ self
72
+ end
73
+
74
+ # pretend to be lazy (see _)
75
+ def chain(m,*a,&b)
76
+ ::Obfusk.lazy { public_send(m,*a,&b) }
77
+ end
78
+
79
+ # --
80
+
81
+ # the list of those elements that satisfy the predicate
82
+ def filter(p = nil, &b)
83
+ g = p || b
84
+ match Nil: -> (_) { Nil() },
85
+ Cons: -> (xs) { g[xs.head] ? Cons(xs.head) { xs.tail.filter(g) }
86
+ : xs.tail.filter(g) }
87
+ end
88
+
89
+ # the list obtained by applying a function (or block) to each element
90
+ def map(f = nil, &b)
91
+ g = f || b
92
+ match Nil: -> (_) { Nil() },
93
+ Cons: -> (xs) { Cons(g[xs.head]) { xs.tail.map g } }
94
+ end
95
+
96
+ # --
97
+
98
+ # element at index
99
+ def [](i)
100
+ raise ArgumentError, 'negative index' if i < 0
101
+ j = 0; each { |x| return x if i == j; j += 1 }
102
+ raise ArgumentError, 'index too large'
103
+ end
104
+
105
+ # length
106
+ def length
107
+ n = 0; each { n += 1 }; n
108
+ end
109
+
110
+ # empty?
111
+ def null
112
+ match Nil: -> (_) { true }, Cons: -> (_) { false }
113
+ end
114
+ alias :null? :null
115
+ alias :empty? :null
116
+
117
+ # --
118
+
119
+ # def last
120
+ # def init
121
+
122
+ # --
123
+
124
+ # append two lists
125
+ def append(ys)
126
+ match Nil: -> (_) { ys._ },
127
+ Cons: -> (xs) { Cons(xs.head) { xs.tail.append ys } }
128
+ end
129
+
130
+ # def reverse
131
+
132
+ # -- Reducing lists (folds) --
133
+
134
+ # def foldl
135
+ # def foldl1
136
+
137
+ # foldr, applied to a binary operator, a starting value (typically
138
+ # the right-identity of the operator), and a list, reduces the
139
+ # list using the binary operator, from right to left:
140
+ #
141
+ # ```haskell
142
+ # foldr f z [x1, x2, ..., xn] == x1 `f` (x2 `f` ... (xn `f` z)...)
143
+ # ```
144
+ #
145
+ # NB: because ruby is not lazy, the secons argument of the binary
146
+ # operator is lazy and must be treated as such.
147
+ def foldr(z, f = nil, &b)
148
+ g = f || b
149
+ match Nil: -> (_) { z },
150
+ Cons: -> (xs) { g[xs.head, xs.tail.chain(:foldr, z, g)] }
151
+ end
152
+
153
+ # def foldr1
154
+
155
+ # -- Special folds --
156
+
157
+ # def and
158
+ # def or
159
+ # def any
160
+
161
+ # concatenate a list of lists
162
+ def concat
163
+ foldr(Nil()) { |x,ys| x.append ys }
164
+ end
165
+
166
+ # def concatMap
167
+
168
+ # def sum
169
+ # def product
170
+ # def maximum
171
+ # def minimum
172
+
173
+ # -- Building lists --
174
+
175
+ # def scanl
176
+ # def scanl1
177
+ # def scanr
178
+ # def scanr1
179
+
180
+ # -- Infinite lists --
181
+
182
+ # def iterate
183
+ # def repeat
184
+ # def replicate
185
+ # def cycle
186
+
187
+ # -- Sublists --
188
+
189
+ # the prefix of length n (or the list itself if n > length)
190
+ def take(n)
191
+ return Nil() if n <= 0
192
+ match Nil: -> (_) { Nil() },
193
+ Cons: -> (xs) { Cons(xs.head) { xs.tail.take(n - 1) } }
194
+ end
195
+
196
+ # def drop
197
+ # def splitAt
198
+ # def takeWhile
199
+ # def dropWhile
200
+ # def span
201
+ # def break
202
+
203
+ # -- Searching lists --
204
+
205
+ # def elem
206
+ # def notElem
207
+ # def lookup
208
+
209
+ # -- Zipping and unzipping lists --
210
+
211
+ # def zip
212
+ # def zip3
213
+
214
+ # combine parallel elements of two lists using a binary operator
215
+ def zipWith(ys, f = nil, &b)
216
+ g = f || b
217
+ self == Nil() || ys._ == Nil() ? Nil() :
218
+ Cons(g[head, ys._.head]) { tail.zipWith(ys._.tail, g) }
219
+ end
220
+
221
+ # def zipWith3
222
+ # def unzip
223
+ # def unzip3
224
+
225
+ # -- Functions on strings --
226
+
227
+ # def lines
228
+ # def words
229
+ # def unlines
230
+ # def unwords
231
+
232
+ # -- Monad --
233
+
234
+ def self.mreturn(x)
235
+ Cons x, Nil()
236
+ end
237
+
238
+ # TODO
239
+ # def self.bind_pass(m, &b)
240
+ # m.match Nil: -> (_) { m },
241
+ # Cons: -> (x) {} # concat (map f m)
242
+ # end
243
+ end
244
+
245
+ List.import_constructors self
246
+
247
+ # create a list from its items; pass a block to add a lazy tail
248
+ def self.List(*xs, &b)
249
+ b && xs.length == 1 ? Cons(xs.first, &b) :
250
+ b && xs.empty? ? b._ :
251
+ xs.empty? ? Nil() :
252
+ Cons(xs.first) { List(*xs.drop(1), &b) }
253
+ end
254
+ end
255
+
256
+ # vim: set tw=70 sw=2 sts=2 et fdm=marker :
@@ -0,0 +1,126 @@
1
+ # -- ; {{{1
2
+ #
3
+ # File : obfusk/monad.rb
4
+ # Maintainer : Felix C. Stegerman <flx@obfusk.net>
5
+ # Date : 2014-06-15
6
+ #
7
+ # Copyright : Copyright (C) 2014 Felix C. Stegerman
8
+ # Licence : LGPLv3+
9
+ #
10
+ # -- ; }}}1
11
+
12
+ require 'obfusk/lazy'
13
+
14
+ module Obfusk
15
+ module Monad
16
+ def self.included(base)
17
+ base.extend ClassMethods
18
+ end
19
+
20
+ module ClassMethods
21
+ # inject a value into the monadic type
22
+ #
23
+ # implement me!
24
+ def mreturn(x)
25
+ raise NotImplementedError
26
+ end
27
+
28
+ # alias for mreturn
29
+ def return(x)
30
+ mreturn x
31
+ end
32
+
33
+ # sequentially compose two actions; passing any value produced
34
+ # by the first as an argument to the second when a block is
35
+ # used; discarding any value produced by the first when no block
36
+ # is used; see bind_pass, bind_discard
37
+ #
38
+ # ```
39
+ # bind(m) { |x| k } # pass value
40
+ # bind m, k # discard value
41
+ # ```
42
+ def bind(m, k = nil, &b)
43
+ b ? bind_pass(m, &b) : bind_discard(m, k)
44
+ end
45
+
46
+ # sequentially compose two actions, passing any value produced
47
+ # by the first as an argument to the second
48
+ #
49
+ # implement me!
50
+ def bind_pass(m, &b)
51
+ raise NotImplementedError
52
+ end
53
+
54
+ # sequentially compose two actions, discarding any value
55
+ # produced by the first
56
+ #
57
+ # implement me!
58
+ def bind_discard(m, k)
59
+ bind_pass(m) { |_| k }
60
+ end
61
+
62
+ # map monad (i.e. functor)
63
+ def fmap(m, f = nil, &b)
64
+ g = f || b; bind(m) { |k| mreturn g[k] }
65
+ end
66
+
67
+ # flatten monad
68
+ def join(m)
69
+ bind(m) { |k| k }
70
+ end
71
+
72
+ # concatenate a sequence of binds
73
+ def pipeline(m, *fs)
74
+ fs.empty? ? m : m.bind { |k| pipeline fs.first[k], *fs.drop(1) }
75
+ end
76
+
77
+ # evaluate each action in the sequence from left to right, and
78
+ # collect the results
79
+ def sequence(*ms)
80
+ ms.inject(mreturn []) do |m,k|
81
+ bind(m) { |xs| bind(k) { |x| mreturn xs+[x] } }
82
+ end
83
+ end
84
+
85
+ # evaluate each action in the sequence from left to right, and
86
+ # ignore the results
87
+ def sequence_(*ms)
88
+ (ms + [mreturn(nil)]).inject { |m,k| bind(m, k) }
89
+ end
90
+ end
91
+
92
+ %w{ bind fmap join pipeline sequence sequence_ }.map(&:to_sym).each do |m|
93
+ define_method(m) do |*a,&b|
94
+ self.class.public_send m, self, *a, &b
95
+ end
96
+ end
97
+ end
98
+
99
+ module MonadPlus
100
+ def self.included(base)
101
+ base.extend ClassMethods
102
+ end
103
+
104
+ module ClassMethods
105
+ # identity
106
+ def zero
107
+ raise NotImplementedError
108
+ end
109
+
110
+ # associative operation
111
+ def plus(m, k = nil, &b)
112
+ lazy_plus m, ::Obfusk.lazy(k, &b)
113
+ end
114
+
115
+ def lazy_plus(m, k)
116
+ raise NotImplementedError
117
+ end
118
+ end
119
+
120
+ def plus(k = nil, &b)
121
+ self.class.plus self, k, &b
122
+ end
123
+ end
124
+ end
125
+
126
+ # vim: set tw=70 sw=2 sts=2 et fdm=marker :
@@ -0,0 +1,60 @@
1
+ # -- ; {{{1
2
+ #
3
+ # File : obfusk/monads.rb
4
+ # Maintainer : Felix C. Stegerman <flx@obfusk.net>
5
+ # Date : 2014-06-15
6
+ #
7
+ # Copyright : Copyright (C) 2014 Felix C. Stegerman
8
+ # Licence : LGPLv3+
9
+ #
10
+ # -- ; }}}1
11
+
12
+ require 'obfusk/adt'
13
+ require 'obfusk/monad'
14
+
15
+ module Obfusk
16
+ class Maybe
17
+ include ADT
18
+ include Monad
19
+ include MonadPlus
20
+
21
+ constructor :Nothing
22
+ constructor :Just, :value
23
+
24
+ def self.mreturn(x)
25
+ Just(x)
26
+ end
27
+ def self.bind_pass(m, &b)
28
+ m.match Nothing: -> (_) { Nothing() },
29
+ Just: -> (x) { b[x.value] }
30
+ end
31
+ def self.zero
32
+ Nothing()
33
+ end
34
+ def self.lazy_plus(m, k)
35
+ m.match Nothing: -> (_) { k._ },
36
+ Just: -> (_) { m }
37
+ end
38
+ end
39
+
40
+ class Either
41
+ include ADT
42
+ include Monad
43
+
44
+ constructor :Left , :value
45
+ constructor :Right, :value
46
+
47
+ def self.mreturn(x)
48
+ Right(x)
49
+ end
50
+ def self.bind_pass(m, &b)
51
+ m.match Left: -> (_) { m },
52
+ Right: -> (x) { b[x.value] }
53
+ end
54
+ end
55
+
56
+ [Maybe, Either].each { |x| x.import_constructors self }
57
+
58
+ end
59
+
60
+ # vim: set tw=70 sw=2 sts=2 et fdm=marker :
@@ -0,0 +1,4 @@
1
+ module Obfusk
2
+ VERSION = '0.1.0'
3
+ DATE = '2014-06-16'
4
+ end
data/lib/obfusk.rb ADDED
@@ -0,0 +1,6 @@
1
+ require 'obfusk/adt'
2
+ require 'obfusk/lazy'
3
+ require 'obfusk/list'
4
+ require 'obfusk/monad'
5
+ require 'obfusk/monads'
6
+ require 'obfusk/version'
data/obfusk.gemspec ADDED
@@ -0,0 +1,28 @@
1
+ require File.expand_path('../lib/obfusk/version', __FILE__)
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = 'obfusk'
5
+ s.homepage = 'https://github.com/obfusk/obfusk.rb'
6
+ s.summary = 'functional programming library for ruby'
7
+
8
+ s.description = <<-END.gsub(/^ {4}/, '')
9
+ ...
10
+ END
11
+
12
+ s.version = Obfusk::VERSION
13
+ s.date = Obfusk::DATE
14
+
15
+ s.authors = [ 'Felix C. Stegerman' ]
16
+ s.email = %w{ flx@obfusk.net }
17
+
18
+ s.licenses = %w{ LGPLv3+ }
19
+
20
+ s.files = %w{ .yardopts README.md Rakefile obfusk.gemspec } \
21
+ + Dir['lib/**/*.rb']
22
+
23
+ s.add_development_dependency 'rake'
24
+ s.add_development_dependency 'rspec'
25
+ s.add_development_dependency 'yard'
26
+
27
+ s.required_ruby_version = '>= 1.9.1'
28
+ end
metadata ADDED
@@ -0,0 +1,99 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: obfusk
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Felix C. Stegerman
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-06-16 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rake
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rspec
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: yard
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ description: |
56
+ ...
57
+ email:
58
+ - flx@obfusk.net
59
+ executables: []
60
+ extensions: []
61
+ extra_rdoc_files: []
62
+ files:
63
+ - ".yardopts"
64
+ - README.md
65
+ - Rakefile
66
+ - lib/obfusk.rb
67
+ - lib/obfusk/adt.rb
68
+ - lib/obfusk/lazy.rb
69
+ - lib/obfusk/list.rb
70
+ - lib/obfusk/monad.rb
71
+ - lib/obfusk/monads.rb
72
+ - lib/obfusk/version.rb
73
+ - obfusk.gemspec
74
+ homepage: https://github.com/obfusk/obfusk.rb
75
+ licenses:
76
+ - LGPLv3+
77
+ metadata: {}
78
+ post_install_message:
79
+ rdoc_options: []
80
+ require_paths:
81
+ - lib
82
+ required_ruby_version: !ruby/object:Gem::Requirement
83
+ requirements:
84
+ - - ">="
85
+ - !ruby/object:Gem::Version
86
+ version: 1.9.1
87
+ required_rubygems_version: !ruby/object:Gem::Requirement
88
+ requirements:
89
+ - - ">="
90
+ - !ruby/object:Gem::Version
91
+ version: '0'
92
+ requirements: []
93
+ rubyforge_project:
94
+ rubygems_version: 2.2.2
95
+ signing_key:
96
+ specification_version: 4
97
+ summary: functional programming library for ruby
98
+ test_files: []
99
+ has_rdoc: