obfusk 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.
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: