fr 0.9.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +0 -0
- data/Rakefile +81 -0
- data/lib/fr.rb +44 -0
- data/lib/fr/applicative.rb +0 -0
- data/lib/fr/array.rb +168 -0
- data/lib/fr/boolean.rb +15 -0
- data/lib/fr/either.rb +94 -0
- data/lib/fr/errors.rb +5 -0
- data/lib/fr/errors/zipper_error.rb +8 -0
- data/lib/fr/functor.rb +11 -0
- data/lib/fr/functor/array.rb +15 -0
- data/lib/fr/functor/either.rb +17 -0
- data/lib/fr/functor/maybe.rb +17 -0
- data/lib/fr/maybe.rb +76 -0
- data/lib/fr/monad.rb +236 -0
- data/lib/fr/monad/array.rb +21 -0
- data/lib/fr/monad/either.rb +18 -0
- data/lib/fr/monad/maybe.rb +24 -0
- data/lib/fr/monad/random.rb +50 -0
- data/lib/fr/monad/reader.rb +45 -0
- data/lib/fr/monad/state.rb +62 -0
- data/lib/fr/monad/writer.rb +49 -0
- data/lib/fr/monoid.rb +52 -0
- data/lib/fr/monoid/array.rb +13 -0
- data/lib/fr/monoid/boolean.rb +23 -0
- data/lib/fr/monoid/either.rb +13 -0
- data/lib/fr/monoid/maybe.rb +15 -0
- data/lib/fr/monoid/numeric.rb +31 -0
- data/lib/fr/monoid/string.rb +11 -0
- data/lib/fr/numeric.rb +1 -0
- data/lib/fr/object.rb +45 -0
- data/lib/fr/string.rb +1 -0
- data/lib/fr/szipper.rb +36 -0
- data/lib/fr/szipper/abstract_cursor.rb +63 -0
- data/lib/fr/szipper/edited_cursor.rb +62 -0
- data/lib/fr/szipper/enumerable.rb +266 -0
- data/lib/fr/szipper/head_cursor.rb +71 -0
- data/lib/fr/szipper/head_prev_cursor.rb +112 -0
- data/lib/fr/szipper/memoized_cursor.rb +59 -0
- data/lib/fr/szipper/node.rb +67 -0
- data/lib/fr/szipper/tail_next_cursor.rb +68 -0
- data/lib/fr/thunk.rb +73 -0
- data/lib/fr/tzipper.rb +38 -0
- data/lib/fr/tzipper/abstract_cursor.rb +351 -0
- data/lib/fr/tzipper/dangling_cursor.rb +107 -0
- data/lib/fr/tzipper/edited_cursor.rb +161 -0
- data/lib/fr/tzipper/memoized_cursor.rb +129 -0
- data/lib/fr/tzipper/node.rb +57 -0
- data/lib/fr/tzipper/path.rb +125 -0
- data/lib/fr/tzipper/root_cursor.rb +124 -0
- data/lib/fr/unfold.rb +93 -0
- data/spec/examples/array.example +0 -0
- data/spec/examples/monads/array.example +0 -0
- data/spec/examples/monads/maybe.example +0 -0
- data/spec/examples/monads/random.example +0 -0
- data/spec/examples/monads/state.example +0 -0
- data/spec/examples/szipper.example +652 -0
- data/spec/examples/thunk.example +0 -0
- data/spec/examples/tzipper.example +0 -0
- data/spec/examples/unfold.example +90 -0
- data/spec/spec_helper.rb +23 -0
- data/spec/support/szipper/model.rb +225 -0
- data/spec/support/szipper/nodel.rb +144 -0
- metadata +116 -0
data/README.md
ADDED
File without changes
|
data/Rakefile
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
require "pathname"
|
2
|
+
abspath = Pathname.new(File.dirname(__FILE__)).expand_path
|
3
|
+
relpath = abspath.relative_path_from(Pathname.pwd)
|
4
|
+
|
5
|
+
begin
|
6
|
+
require "rubygems"
|
7
|
+
require "bundler/setup"
|
8
|
+
rescue LoadError
|
9
|
+
warn "couldn't load bundler:"
|
10
|
+
warn " #{$!}"
|
11
|
+
end
|
12
|
+
|
13
|
+
task :console do
|
14
|
+
exec *%w(irb -I lib -r fr)
|
15
|
+
end
|
16
|
+
|
17
|
+
begin
|
18
|
+
require "rspec/core/rake_task"
|
19
|
+
RSpec::Core::RakeTask.new do |t|
|
20
|
+
t.verbose = false
|
21
|
+
t.pattern = "#{relpath}/spec/examples/**/*.example"
|
22
|
+
|
23
|
+
t.rspec_opts = %w(--color --format p)
|
24
|
+
t.rspec_opts << "-I#{abspath}/spec"
|
25
|
+
end
|
26
|
+
rescue LoadError
|
27
|
+
task :spec do
|
28
|
+
warn "couldn't load rspec"
|
29
|
+
warn " #{$!}"
|
30
|
+
exit 1
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
begin
|
35
|
+
require "rcov"
|
36
|
+
begin
|
37
|
+
require "rspec/core/rake_task"
|
38
|
+
RSpec::Core::RakeTask.new(:rcov) do |t|
|
39
|
+
t.rcov = true
|
40
|
+
t.rcov_opts = "--exclude spec/,gems/"
|
41
|
+
|
42
|
+
t.verbose = false
|
43
|
+
t.pattern = "#{relpath}/spec/examples/**/*.example"
|
44
|
+
|
45
|
+
t.rspec_opts = %w(--color --format p)
|
46
|
+
t.rspec_opts << "-I#{abspath}/spec"
|
47
|
+
end
|
48
|
+
rescue LoadError
|
49
|
+
task :rcov do
|
50
|
+
warn "couldn't load rspec"
|
51
|
+
warn " #{$!}"
|
52
|
+
exit 1
|
53
|
+
end
|
54
|
+
end
|
55
|
+
rescue LoadError
|
56
|
+
task :rcov do
|
57
|
+
warn "couldn't load rcov:"
|
58
|
+
warn " #{$!}"
|
59
|
+
exit 1
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
begin
|
64
|
+
require "yard"
|
65
|
+
|
66
|
+
# Note options are loaded from .yardopts
|
67
|
+
YARD::Rake::YardocTask.new(:yard => :clobber_yard)
|
68
|
+
|
69
|
+
task :clobber_yard do
|
70
|
+
rm_rf "#{relpath}/doc/generated"
|
71
|
+
mkdir_p "#{relpath}/doc/generated/images"
|
72
|
+
end
|
73
|
+
rescue LoadError
|
74
|
+
task :yard do
|
75
|
+
warn "couldn't load yard:"
|
76
|
+
warn " #{$!}"
|
77
|
+
exit 1
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
task :default => :spec
|
data/lib/fr.rb
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
module Fr
|
2
|
+
autoload :Errors, "fr/errors"
|
3
|
+
autoload :Maybe, "fr/maybe"
|
4
|
+
autoload :Either, "fr/either"
|
5
|
+
autoload :Functor, "fr/functor"
|
6
|
+
autoload :Monad, "fr/monad"
|
7
|
+
autoload :Monoid, "fr/monoid"
|
8
|
+
autoload :Additive, "fr/monoid/numeric"
|
9
|
+
autoload :Multiplicitive, "fr/monoid/numeric"
|
10
|
+
autoload :Random, "fr/monad/random"
|
11
|
+
autoload :State, "fr/monad/state"
|
12
|
+
autoload :Reader, "fr/monad/reader"
|
13
|
+
autoload :Writer, "fr/monad/writer"
|
14
|
+
autoload :SZipper, "fr/szipper"
|
15
|
+
autoload :TZipper, "fr/tzipper"
|
16
|
+
autoload :Thunk, "fr/thunk"
|
17
|
+
|
18
|
+
def self.thunk(&block)
|
19
|
+
Thunk.new(block)
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.some(value)
|
23
|
+
Fr::Maybe::Some.new(value)
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.none
|
27
|
+
Fr::Maybe::None
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.left(value)
|
31
|
+
Fr::Either::Left.new(value)
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.right(value)
|
35
|
+
Fr::Either::Right.new(value)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
require "fr/array"
|
40
|
+
require "fr/boolean"
|
41
|
+
require "fr/object"
|
42
|
+
require "fr/unfold"
|
43
|
+
require "fr/string"
|
44
|
+
require "fr/numeric"
|
File without changes
|
data/lib/fr/array.rb
ADDED
@@ -0,0 +1,168 @@
|
|
1
|
+
class Array
|
2
|
+
|
3
|
+
# Return the first item. Raises an `IndexError` if the Array is `empty?`.
|
4
|
+
#
|
5
|
+
# @example
|
6
|
+
# [1, 2, 3].head #=> 1
|
7
|
+
#
|
8
|
+
def head
|
9
|
+
raise IndexError, "head of empty list" if empty?
|
10
|
+
x, = self
|
11
|
+
x
|
12
|
+
end
|
13
|
+
|
14
|
+
# True if `#at` is defined for the given `n`
|
15
|
+
#
|
16
|
+
# @example
|
17
|
+
# [1, 2, 3].defined_at?(0) #=> true
|
18
|
+
# [].defined_at?(0) #=> false
|
19
|
+
#
|
20
|
+
def defined_at?(n)
|
21
|
+
n < length and -n <= length
|
22
|
+
end
|
23
|
+
|
24
|
+
# @group Selection
|
25
|
+
#############################################################################
|
26
|
+
|
27
|
+
# Selects all elements except the first.
|
28
|
+
#
|
29
|
+
# @example
|
30
|
+
# [1, 2, 3].tail #=> [2, 3]
|
31
|
+
# [1].tail #=> []
|
32
|
+
# [].tail #=> []
|
33
|
+
#
|
34
|
+
# @return [Array]
|
35
|
+
def tail
|
36
|
+
_, *xs = self
|
37
|
+
xs
|
38
|
+
end
|
39
|
+
|
40
|
+
# Selects all elements except the last `n` ones.
|
41
|
+
#
|
42
|
+
# @example
|
43
|
+
# [1, 2, 3].init #=> [1, 2]
|
44
|
+
# [1, 2, 3].init(2) #=> [1]
|
45
|
+
# [].tail #=> []
|
46
|
+
#
|
47
|
+
# @return [Array]
|
48
|
+
def init(n = 1)
|
49
|
+
raise ArgumentError, "n cannot be negative" if n < 0
|
50
|
+
slice(0..-(n + 1)) or []
|
51
|
+
end
|
52
|
+
|
53
|
+
# Select all elements except the first `n` ones.
|
54
|
+
#
|
55
|
+
# @example
|
56
|
+
# [1, 2, 3].drop(1) #=> [2, 3]
|
57
|
+
# [1, 3, 3].drop(2) #=> [3]
|
58
|
+
# [].drop(10) #=> []
|
59
|
+
#
|
60
|
+
# @return [Array]
|
61
|
+
def drop(n)
|
62
|
+
raise ArgumentError, "n cannot be negative" if n < 0
|
63
|
+
slice(n..-1) or []
|
64
|
+
end
|
65
|
+
|
66
|
+
# Select the first `n` elements.
|
67
|
+
#
|
68
|
+
# @example
|
69
|
+
# [1, 2, 3].take(2) #=> [1, 2]
|
70
|
+
# [1, 2, 3].take(0) #=> []
|
71
|
+
#
|
72
|
+
# @return [Array]
|
73
|
+
def take(n)
|
74
|
+
raise ArgumentError, "n cannot be negative" if n < 0
|
75
|
+
slice(0, n) or []
|
76
|
+
end
|
77
|
+
|
78
|
+
# Split the array in two at the given position.
|
79
|
+
#
|
80
|
+
# @example
|
81
|
+
# [1, 2, 3].split_at(2) #=> [[1,2], [3]]
|
82
|
+
# [1, 2, 3].split_at(0) #=> [[], [1,2,3]]
|
83
|
+
#
|
84
|
+
# @return [(Array, Array)]
|
85
|
+
def split_at(n)
|
86
|
+
n = length + n if n < 0
|
87
|
+
return take(n), drop(n)
|
88
|
+
end
|
89
|
+
|
90
|
+
# @endgroup
|
91
|
+
#############################################################################
|
92
|
+
|
93
|
+
# @group Filtering
|
94
|
+
#############################################################################
|
95
|
+
|
96
|
+
# Drops the longest prefix of elements that satisfy the predicate.
|
97
|
+
#
|
98
|
+
# @return [Array]
|
99
|
+
def drop_while(&block)
|
100
|
+
# This is in tail call form
|
101
|
+
if not empty? and yield(head)
|
102
|
+
tail.drop_while(&block)
|
103
|
+
else
|
104
|
+
self
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
# Drops the longest prefix of elements that do not satisfy the predicate.
|
109
|
+
#
|
110
|
+
# @return [Array]
|
111
|
+
def drop_until(&block)
|
112
|
+
# This is in tail call form
|
113
|
+
unless empty? or yield(head)
|
114
|
+
tail.drop_until(&block)
|
115
|
+
else
|
116
|
+
self
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
# Takes the longest prefix of elements that satisfy the predicate.
|
121
|
+
#
|
122
|
+
# @return [Array]
|
123
|
+
def take_while(accumulator = [], &block)
|
124
|
+
# This is in tail call form
|
125
|
+
if not empty? and yield(head)
|
126
|
+
tail.take_while(head.snoc(accumulator), &block)
|
127
|
+
else
|
128
|
+
accumulator
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
# Takes the longest prefix of elements that do not satisfy the predicate.
|
133
|
+
#
|
134
|
+
# @return [Array]
|
135
|
+
def take_until(accumulator = [], &block)
|
136
|
+
# This is in tail call form
|
137
|
+
unless empty? or yield(head)
|
138
|
+
tail.take_until(head.snoc(accumulator), &block)
|
139
|
+
else
|
140
|
+
accumulator
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
# Splits the array into prefix/suffix pair according to the predicate.
|
145
|
+
#
|
146
|
+
# @return [(Array, Array)]
|
147
|
+
def split_until(&block)
|
148
|
+
prefix = take_while(&block)
|
149
|
+
suffix = drop(prefix.length)
|
150
|
+
return prefix, suffix
|
151
|
+
end
|
152
|
+
|
153
|
+
# Splits the array into prefix/suffix pair according to the predicate.
|
154
|
+
#
|
155
|
+
# @return [(Array, Array)]
|
156
|
+
def split_when(&block)
|
157
|
+
prefix = take_until(&block)
|
158
|
+
suffix = drop(prefix.length)
|
159
|
+
return prefix, suffix
|
160
|
+
end
|
161
|
+
|
162
|
+
# @endgroup
|
163
|
+
#############################################################################
|
164
|
+
end
|
165
|
+
|
166
|
+
require "fr/functor/array"
|
167
|
+
require "fr/monoid/array"
|
168
|
+
require "fr/monad/array"
|
data/lib/fr/boolean.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
class TrueClass
|
2
|
+
# true.maybe(value) => Fr::Some(value)
|
3
|
+
def maybe(value)
|
4
|
+
Fr::Maybe::Some.new(value)
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
class FalseClass
|
9
|
+
# false.maybe(value) => Fr::None
|
10
|
+
def maybe(value)
|
11
|
+
Fr::Maybe::None
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
require "fr/monoid/boolean"
|
data/lib/fr/either.rb
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
module Fr
|
2
|
+
|
3
|
+
class Either
|
4
|
+
|
5
|
+
class << self
|
6
|
+
# :: [Either a b] -> [a]
|
7
|
+
def lefts(es)
|
8
|
+
es.inject([]) do |ls,e|
|
9
|
+
e.fold(lambda{|l| ls.push(l) }, lambda{|_| ls })
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
# :: [Either a b] -> [b]
|
14
|
+
def rights(es)
|
15
|
+
es.inject([]) do |rs,e|
|
16
|
+
e.fold(lambda{|_| rs }, lambda{|r| rs.push(r) })
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# :: [Either a b] -> [a] -> [b]
|
21
|
+
def partition(es)
|
22
|
+
es.inject([[],[]]) do |(ls,rs),e|
|
23
|
+
e.fold(lambda{|l| [ls.push(l), rs] },
|
24
|
+
lambda{|r| [ls, rs.push(r)] })
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
class Left < Either
|
30
|
+
def initialize(value)
|
31
|
+
@value = value
|
32
|
+
end
|
33
|
+
|
34
|
+
# :: Either a b -> (a -> c) -> (b -> c) -> c
|
35
|
+
def fold(left, right)
|
36
|
+
left.call(@value)
|
37
|
+
end
|
38
|
+
|
39
|
+
def map(&f)
|
40
|
+
self
|
41
|
+
end
|
42
|
+
|
43
|
+
def right?
|
44
|
+
false
|
45
|
+
end
|
46
|
+
|
47
|
+
def left?
|
48
|
+
true
|
49
|
+
end
|
50
|
+
|
51
|
+
def eql?(other)
|
52
|
+
Either === other and
|
53
|
+
other.fold(lambda{|o| o == @value }, lambda{|_| false })
|
54
|
+
end
|
55
|
+
|
56
|
+
alias_method :==, :eql?
|
57
|
+
end
|
58
|
+
|
59
|
+
class Right < Either
|
60
|
+
def initialize(value)
|
61
|
+
@value = value
|
62
|
+
end
|
63
|
+
|
64
|
+
# :: Either a b -> (a -> c) -> (b -> c) -> c
|
65
|
+
def fold(left, right)
|
66
|
+
right.call(@value)
|
67
|
+
end
|
68
|
+
|
69
|
+
def map(&f)
|
70
|
+
Right.new(f.call(@value))
|
71
|
+
end
|
72
|
+
|
73
|
+
def right?
|
74
|
+
true
|
75
|
+
end
|
76
|
+
|
77
|
+
def left?
|
78
|
+
false
|
79
|
+
end
|
80
|
+
|
81
|
+
def eql?(other)
|
82
|
+
Either === other and
|
83
|
+
other.fold(lambda{|_| false }, lambda{|o| o == @value })
|
84
|
+
end
|
85
|
+
|
86
|
+
alias_method :==, :eql?
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
91
|
+
|
92
|
+
require "fr/functor/either"
|
93
|
+
require "fr/monoid/either"
|
94
|
+
require "fr/monad/either"
|
data/lib/fr/errors.rb
ADDED
data/lib/fr/functor.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
#
|
2
|
+
# Because Array is a monad, we automatically derive
|
3
|
+
# the implementation for `map` from `liftM`.
|
4
|
+
#
|
5
|
+
# class << Array
|
6
|
+
# include Fr::Functor
|
7
|
+
#
|
8
|
+
# def map(f = nil, x = nil, &block)
|
9
|
+
# (x.nil?) ?
|
10
|
+
# (block_given?) ?
|
11
|
+
# f.map(&block) : # Array.map([..]){ .. }
|
12
|
+
# lambda{|x| x.map(&f) } :# Array.map(lambda{ .. })
|
13
|
+
# x.map(&f) # Array.map(lambda{ .. }, [..])
|
14
|
+
# end
|
15
|
+
# end
|