Skip to content

Commit 7536f03

Browse files
alex-lairanclaude
andcommitted
Bump version to 1.2.0 with fold method and specs
- Add fold method to Either (Right, Left, LeftException) - Add fold method to Maybe (Just, Nothing) - Add comprehensive specs for fold on all types - Fix parameter name warnings across Either, Maybe, and List Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent b9085b7 commit 7536f03

File tree

11 files changed

+168
-48
lines changed

11 files changed

+168
-48
lines changed

shard.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
name: monads
2-
version: 1.1.0
2+
version: 1.2.0
33

44
authors:
55
- Alexandre Lairan <alexandrelairan+github@gmail.com>

spec/monads/either/left_exception_spec.cr

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,4 +176,14 @@ describe Monads::LeftException do
176176
value.should eq('a')
177177
end
178178
end
179+
180+
describe "#fold" do
181+
it "#fold with two procs applies left_fn to exception" do
182+
result = Monads::LeftException(Int32).new(DivisionByZeroError.new).fold(
183+
->(x : Int32) { "success: #{x}" },
184+
->(e : Exception) { "error: #{e.class.name}" }
185+
)
186+
result.should eq("error: DivisionByZeroError")
187+
end
188+
end
179189
end

spec/monads/either/left_spec.cr

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,4 +219,20 @@ describe Monads::Left do
219219
value.should eq('a')
220220
end
221221
end
222+
223+
describe "#fold" do
224+
it "#fold with two procs applies left_fn to error value" do
225+
result = Monads::Left(String, Int32).new("error message").fold(
226+
->(x : Int32) { "success: #{x}" },
227+
->(e : String) { "error: #{e}" }
228+
)
229+
result.should eq("error: error message")
230+
end
231+
232+
it "#fold with block raises error" do
233+
expect_raises(Exception, "Called fold on Left") do
234+
Monads::Left(String, Int32).new("error").fold { |x| x * 2 }
235+
end
236+
end
237+
end
222238
end

spec/monads/either/right_spec.cr

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,4 +234,19 @@ describe Monads::Right do
234234
value.should eq('a')
235235
end
236236
end
237+
238+
describe "#fold" do
239+
it "#fold with two procs applies right_fn to value" do
240+
result = Monads::Right(String, Int32).new(42).fold(
241+
->(x : Int32) { "success: #{x}" },
242+
->(e : String) { "error: #{e}" }
243+
)
244+
result.should eq("success: 42")
245+
end
246+
247+
it "#fold with block applies block to value" do
248+
result = Monads::Right(String, Int32).new(42).fold { |x| x * 2 }
249+
result.should eq(84)
250+
end
251+
end
237252
end

spec/monads/maybe/just_spec.cr

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,4 +214,19 @@ describe Monads::Just do
214214
monad.should eq(Monads::Just.new(1))
215215
end
216216
end
217+
218+
describe "#fold" do
219+
it "#fold with two procs applies just_fn to value" do
220+
result = Monads::Just.new(42).fold(
221+
->(x : Int32) { "value: #{x}" },
222+
->{ "nothing" }
223+
)
224+
result.should eq("value: 42")
225+
end
226+
227+
it "#fold with block applies block to value" do
228+
result = Monads::Just.new(42).fold { |x| x * 2 }
229+
result.should eq(84)
230+
end
231+
end
217232
end

spec/monads/maybe/nothing_spec.cr

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,4 +194,20 @@ describe Monads::Nothing do
194194
boolean.should be_falsey
195195
end
196196
end
197+
198+
describe "#fold" do
199+
it "#fold with two procs applies nothing_fn" do
200+
result = Monads::Nothing(Int32).new.fold(
201+
->(x : Int32) { "value: #{x}" },
202+
->{ "nothing" }
203+
)
204+
result.should eq("nothing")
205+
end
206+
207+
it "#fold with block raises error" do
208+
expect_raises(Exception, "Called fold on Nothing") do
209+
Monads::Nothing(Int32).new.fold { |x| x * 2 }
210+
end
211+
end
212+
end
197213
end

spec/monads_spec.cr

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,6 @@ require "./monads/**"
33

44
describe Monads do
55
it "should have version" do
6-
Monads::VERSION.should eq("1.1.0")
6+
Monads::VERSION.should eq("1.2.0")
77
end
88
end

src/monads.cr

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
require "./monads/*"
22

33
module Monads
4-
VERSION = "1.1.0"
4+
VERSION = "1.2.0"
55
end

src/monads/either.cr

Lines changed: 62 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,25 @@ module Monads
2626
Right(Nil, T).new(value)
2727
end
2828

29-
abstract def value_or(element : U) forall U
30-
abstract def value_or(lambda : E -> U) forall U
31-
abstract def or(monad : Either)
32-
abstract def or(lambda : E -> U) forall U
29+
# Fold/match the Either: applies right_fn if Right, left_fn if Left
30+
# Returns the result of whichever function was applied
31+
#
32+
# Example:
33+
# result.fold(
34+
# ->(account : Account) { json_response(account) },
35+
# ->(error : AuthError) { error_response(error) }
36+
# )
37+
abstract def fold(right_fn : T -> U, left_fn : E -> U) forall U
38+
39+
# Block version of fold
40+
def fold(&block : T -> U) forall U
41+
fold(block, ->(e : E) { raise "Called fold on Left" })
42+
end
43+
44+
abstract def value_or(other : U) forall U
45+
abstract def value_or(other : E -> U) forall U
46+
abstract def or(other : Either)
47+
abstract def or(other : E -> U) forall U
3348
abstract def <=>(other : Right)
3449
abstract def <=>(other : Left)
3550
abstract def map_or(default : U, lambda : T -> U) forall U
@@ -51,6 +66,10 @@ module Monads
5166
@data
5267
end
5368

69+
def fold(right_fn : T -> U, left_fn : E -> U) forall U
70+
right_fn.call(@data)
71+
end
72+
5473
def fmap(lambda : T -> U) : Right(E, U) forall U
5574
Right(E, U).new(lambda.call(@data))
5675
end
@@ -67,15 +86,19 @@ module Monads
6786
1
6887
end
6988

70-
def value_or(element : _)
89+
def value_or(other : _)
90+
value!
91+
end
92+
93+
def value_or(other : E -> _)
7194
value!
7295
end
7396

74-
def or(monad : Either)
97+
def or(other : Either)
7598
self
7699
end
77100

78-
def or(lambda : _ -> _) : Right(E, T)
101+
def or(other : _ -> _) : Right(E, T)
79102
self
80103
end
81104

@@ -97,20 +120,20 @@ module Monads
97120
self.as(Leftable(E, U))
98121
end
99122

100-
def value_or(lambda : E -> _)
101-
lambda.call(@data)
123+
def value_or(other : E -> _)
124+
other.call(@data)
102125
end
103126

104-
def value_or(element : U) forall U
105-
element
127+
def value_or(other : U) forall U
128+
other
106129
end
107130

108-
def or(monad : Either)
109-
monad
131+
def or(other : Either)
132+
other
110133
end
111134

112-
def or(lambda : E -> _)
113-
lambda.call(@data)
135+
def or(other : E -> _)
136+
other.call(@data)
114137
end
115138

116139
def bind(lambda : T -> _) : Leftable(E, T)
@@ -132,6 +155,10 @@ module Monads
132155
@data
133156
end
134157

158+
def fold(right_fn : T -> U, left_fn : E -> U) forall U
159+
left_fn.call(@data)
160+
end
161+
135162
def <=>(other : LeftException)
136163
1
137164
end
@@ -152,20 +179,20 @@ module Monads
152179
Left(E, U).new(@data)
153180
end
154181

155-
def value_or(lambda : E -> _)
156-
lambda.call(@data)
182+
def value_or(other : E -> _)
183+
other.call(@data)
157184
end
158185

159-
def value_or(element : U) forall U
160-
element
186+
def value_or(other : U) forall U
187+
other
161188
end
162189

163-
def or(monad : Either)
164-
monad
190+
def or(other : Either)
191+
other
165192
end
166193

167-
def or(lambda : E -> _)
168-
lambda.call(@data)
194+
def or(other : E -> _)
195+
other.call(@data)
169196
end
170197

171198
def bind(lambda : T -> _) : Left(E, T)
@@ -191,6 +218,10 @@ module Monads
191218
@data
192219
end
193220

221+
def fold(right_fn : T -> U, left_fn : Exception -> U) forall U
222+
left_fn.call(@data)
223+
end
224+
194225
def <=>(other : LeftException)
195226
if @data.class == other.value!.class
196227
0
@@ -215,20 +246,20 @@ module Monads
215246
LeftException(U).new(@data)
216247
end
217248

218-
def value_or(lambda : Exception -> _)
219-
lambda.call(@data)
249+
def value_or(other : Exception -> _)
250+
other.call(@data)
220251
end
221252

222-
def value_or(element : U) forall U
223-
element
253+
def value_or(other : U) forall U
254+
other
224255
end
225256

226-
def or(monad : Either)
227-
monad
257+
def or(other : Either)
258+
other
228259
end
229260

230-
def or(lambda : Exception -> _)
231-
lambda.call(@data)
261+
def or(other : Exception -> _)
262+
other.call(@data)
232263
end
233264

234265
def bind(lambda : T -> _) : LeftException(T)

src/monads/list.cr

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ module Monads
3030
end
3131
end
3232

33-
def <=>(rhs : List)
34-
@value <=> rhs.to_a
33+
def <=>(other : List)
34+
@value <=> other.to_a
3535
end
3636

3737
def unsafe_fetch(index : Int)

0 commit comments

Comments
 (0)