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 +7 -0
- data/.yardopts +1 -0
- data/README.md +123 -0
- data/Rakefile +58 -0
- data/lib/obfusk/adt.rb +172 -0
- data/lib/obfusk/lazy.rb +37 -0
- data/lib/obfusk/list.rb +256 -0
- data/lib/obfusk/monad.rb +126 -0
- data/lib/obfusk/monads.rb +60 -0
- data/lib/obfusk/version.rb +4 -0
- data/lib/obfusk.rb +6 -0
- data/obfusk.gemspec +28 -0
- metadata +99 -0
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
|
+
[](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 :
|
data/lib/obfusk/lazy.rb
ADDED
@@ -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 :
|
data/lib/obfusk/list.rb
ADDED
@@ -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 :
|
data/lib/obfusk/monad.rb
ADDED
@@ -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 :
|
data/lib/obfusk.rb
ADDED
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:
|