Skip to content

Commit ddbb18f

Browse files
committed
fn: fix Result[T].FlatMap and add OrElse
For a Result[T], FlatMap should apply f when the result is Ok, and propagate the error unchanged when it’s Err. The original code returns r on Ok and tries to use r.left when Err, which is wrong. This commit fixes that. It also adds an OrElse func that complements the AndThen func that was already present. Secondly, the group of FlatMap/AndThen and OrElse functions are now properly tested with new unit tests. fixes #10401
1 parent 85a5bf2 commit ddbb18f

File tree

2 files changed

+124
-2
lines changed

2 files changed

+124
-2
lines changed

fn/result.go

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -149,10 +149,10 @@ func FlattenResult[A any](r Result[Result[A]]) Result[A] {
149149
// success value if it exists.
150150
func (r Result[T]) FlatMap(f func(T) Result[T]) Result[T] {
151151
if r.IsOk() {
152-
return r
152+
return f(r.left)
153153
}
154154

155-
return f(r.left)
155+
return r
156156
}
157157

158158
// AndThen is an alias for FlatMap. This along with OrElse can be used to
@@ -189,6 +189,17 @@ func AndThen[A, B any](r Result[A], f func(A) Result[B]) Result[B] {
189189
return FlatMapResult(r, f)
190190
}
191191

192+
// OrElse returns the original Result if it is a success, otherwise it returns
193+
// the provided alternative Result. This along with AndThen can be used to
194+
// Railway Oriented Programming (ROP).
195+
func OrElse[A any](r Result[A], f func(error) Result[A]) Result[A] {
196+
if r.IsOk() {
197+
return r
198+
}
199+
200+
return f(r.right)
201+
}
202+
192203
// LiftA2Result lifts a two-argument function to a function that can operate
193204
// over results of its arguments.
194205
func LiftA2Result[A, B, C any](f func(A, B) C,

fn/result_test.go

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,3 +96,114 @@ func TestSinkOnOkContinuationCall(t *testing.T) {
9696
require.True(t, called)
9797
require.Nil(t, res)
9898
}
99+
100+
func TestFlatMap(t *testing.T) {
101+
// Case 1: Ok -> Ok
102+
r1 := Ok(1).FlatMap(func(i int) Result[int] {
103+
return Ok(i + 1)
104+
})
105+
require.Equal(t, Ok(2), r1)
106+
107+
// Case 2: Ok -> Err
108+
err := errors.New("fail")
109+
r2 := Ok(1).FlatMap(func(i int) Result[int] {
110+
return Err[int](err)
111+
})
112+
require.Equal(t, Err[int](err), r2)
113+
114+
// Case 3: Err -> Err (function not called)
115+
origErr := errors.New("original")
116+
r3 := Err[int](origErr).FlatMap(func(i int) Result[int] {
117+
return Ok(i + 1)
118+
})
119+
require.Equal(t, Err[int](origErr), r3)
120+
}
121+
122+
func TestAndThenMethod(t *testing.T) {
123+
// Case 1: Ok -> Ok
124+
r1 := Ok(1).AndThen(func(i int) Result[int] {
125+
return Ok(i + 1)
126+
})
127+
require.Equal(t, Ok(2), r1)
128+
129+
// Case 2: Err -> Err
130+
origErr := errors.New("original")
131+
r2 := Err[int](origErr).AndThen(func(i int) Result[int] {
132+
return Ok(i + 1)
133+
})
134+
require.Equal(t, Err[int](origErr), r2)
135+
}
136+
137+
func TestOrElseMethod(t *testing.T) {
138+
// Case 1: Ok -> Ok (function not called)
139+
r1 := Ok(1).OrElse(func(err error) Result[int] {
140+
return Ok(2)
141+
})
142+
require.Equal(t, Ok(1), r1)
143+
144+
// Case 2: Err -> Ok
145+
origErr := errors.New("original")
146+
r2 := Err[int](origErr).OrElse(func(err error) Result[int] {
147+
return Ok(2)
148+
})
149+
require.Equal(t, Ok(2), r2)
150+
151+
// Case 3: Err -> Err
152+
newErr := errors.New("new")
153+
r3 := Err[int](origErr).OrElse(func(err error) Result[int] {
154+
return Err[int](newErr)
155+
})
156+
require.Equal(t, Err[int](newErr), r3)
157+
}
158+
159+
func TestFlatMapResult(t *testing.T) {
160+
// Case 1: Ok[int] -> Ok[string]
161+
r1 := FlatMapResult(Ok(1), func(i int) Result[string] {
162+
return Ok(fmt.Sprintf("%d", i))
163+
})
164+
require.Equal(t, Ok("1"), r1)
165+
166+
// Case 2: Ok[int] -> Err[string]
167+
err := errors.New("fail")
168+
r2 := FlatMapResult(Ok(1), func(i int) Result[string] {
169+
return Err[string](err)
170+
})
171+
require.Equal(t, Err[string](err), r2)
172+
173+
// Case 3: Err[int] -> Err[string]
174+
origErr := errors.New("original")
175+
r3 := FlatMapResult(Err[int](origErr), func(i int) Result[string] {
176+
return Ok("should not happen")
177+
})
178+
require.Equal(t, Err[string](origErr), r3)
179+
}
180+
181+
func TestAndThenFunc(t *testing.T) {
182+
// Case 1: Ok[int] -> Ok[string]
183+
r1 := AndThen(Ok(1), func(i int) Result[string] {
184+
return Ok(fmt.Sprintf("%d", i))
185+
})
186+
require.Equal(t, Ok("1"), r1)
187+
188+
// Case 2: Err[int] -> Err[string]
189+
origErr := errors.New("original")
190+
r2 := AndThen(Err[int](origErr), func(i int) Result[string] {
191+
return Ok("should not happen")
192+
})
193+
require.Equal(t, Err[string](origErr), r2)
194+
}
195+
196+
func TestOrElseFunc(t *testing.T) {
197+
// Case 1: Ok -> Ok
198+
r1 := OrElse(Ok(1), func(err error) Result[int] {
199+
return Ok(2)
200+
})
201+
require.Equal(t, Ok(1), r1)
202+
203+
// Case 2: Err -> Ok
204+
origErr := errors.New("original")
205+
r2 := OrElse(Err[int](origErr), func(err error) Result[int] {
206+
return Ok(2)
207+
})
208+
require.Equal(t, Ok(2), r2)
209+
}

0 commit comments

Comments
 (0)