fr 0.9.1
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.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
|