Hey folks, I'm watching Scott Wlaschin's 2015 talk...
# arrow
t
Hey folks, I'm watching Scott Wlaschin's 2015 talk on Railway oriented programming. He mentions dead end functions which can be incorporated into a composition (tap and tee). Is there an equivalent in Arrow ... or is simply doing
.map { someOp(); it }
the approach?
s
You mean like a
forEach
or
also
type of functionality executing a pure side-effect (no expected results, just an action) Usually, the method
fold
is used for that.
h
There is
flatTap
that let you compose if your someOp() returns an effect
if your someOp() doesn't trigger a side effect, it doesn't have much sense to call it and then drop the result...
p
yep,
flatTap
to apply an effect and ignore the result
t
I don't have a specific use case to solve at the moment more just learning and thinking ahead. My envisioned use case would be something like this:
Copy code
Ether.right(listOf(1,2,3))()
  .map {
    it * 10
  } // [10, 20, 30]
  .TAP/TEE {
    saveToDatbase(it)
  }
  .map {
    it * 10
  } // [100, 200, 300]
p
map
shouldn’t be used for side-effects conceptually
t
so I guess that .TAP/.TEE should be .flatTap in that above exampel?
p
exactly
t
What if the saveToDatabase was effectful?
p
the most common use case is logging
any writing really
flatTap requires an Either/IO
t
ya sorry my saveToDb probably wasn't a great example 🙂
p
so
.flatTap { IO { saveToDb(it) } }
for IO-based chains
for Either-based ones
same
.flatTap { saveToDb(it).right() }
or
Either.catch
or something similar
if you check Simon’s PR from a couple of messages ago
t
I haven't used IO before so will need to dig into that, but it broadly makese sense
p
it’s even simpler with fx
t
ya was just looking at it actually
p
Copy code
Either.fx {
  val a = !someApiForEither()
  !log(a)
  a + 1
}
t
So in that example, if I put the code snippet into a flatTap it would compose as I am currently use to (given my limited exposure to date!)
p
check flatTap’s implementation
it’ll make everything clear 😉
t
just in the middle of putting some code together 🙂
to experiment
p
t
haha yes the flatTap implementation is striaghtforward 🙂
Hmm i must be missing something here. So i've whipped this together:
Copy code
val result: Either<String, Int> = randomEither(1)
    .map {
        it * 10
    }
    .flatTap {
        println(it)
    }
    .map {
        it * 10
    }

println(result)

fun randomEither(value: Int): Either<String, Int> {
    return if(Random.nextBoolean()) {
        "fail".left()
    } else {
        value.right()
    }
}
But the println is giving this error:
If i wrap it in Either.fx { println(it) } then it works fine ... but Either.fx returns an Either unless I'm mistaken? whereas I was looking to simply do something inside flatTap without having to return another type... otherwise I'd just use map{ println(it); it }
p
you’re trying to do a side-effect in a pure function
b
Monad sequencing requires consistency in the monad
☝🏼 1
p
which sounds pedantic in the vaccum
t
ahh is
Unit
a side effect?
p
yep!
b
any f(...) -> Unit is a side effect (or useless computation)
t
kk that makes sense ... ill try () -> int inside flatTap then!
b
The unit value itself isn't a side effect, but any function that produces the singleton value has to be performing a side effect to be useful
t
ya i can see the argument for Unit being a side effect
b
Actually Either.catch(println(...)) in there would probably work
p
that is because no implementation that returns Unit does anything meaningful other than interacting with the environment or mutating state
b
(or makes the CPU very 🔥)
😄 1
p
I’m super late hahah
t
lol
b
Anyway, if you have a Monad<A> then .flatTap requires A -> Monad<Unit> and returns a Monad<A>
so "you have this thing in the monad context, you can do some side-effect with it but you're getting back the same context you started with prior to your side effect"
p
Either.fx { sideEffect() }
in 0.10 is kind of a trap because
effect
is eager, so you are side-effecting
b
"and you'd better give me the same Monad back from your side effect"
p
Either.fx { sideEffect(); 1 } returns 1 and does the side effect.
Either.catch{ sideEffect(); 1 }
can only be called from a suspend function, so it’s always lazy
hahaha I’ll leave bob talk
I’m just adding to the confusion
b
No, you're doing good, I'm hoping you correct all my mistakes 😄
It's either that or I'm digging into un-observable Selenium to track down regression-test failures in user acceptance code ><
😕 1
😄 1
t
if i only run a pure function inside flatTap I'm still getting an error ... so i've got to return
Either.right(it)
for example ... so why wouldn't i just use
map { somePureFunction(); it }
... Referring back to my original question ref Scott Wlaschin's Tee/Tap form his talk
b
.flatTap()
is to primarily support side-effects
In order to support side-effects it has to take a function that returns something that encapsulates a side-effect
t
ahh kk sorry then thats my misunderstanding of what flatTap does
b
So
A
cannot support side-effects, but
M<A>
can
that's why
.flatTap
takes an
A -> M<Unit>
and that's why your code needs to return
Either
well, technically it needs to return an
Either<L, ...>
, because
Either
is only monadic if
L
is fixed.
t
aww so if i returned
"a-string".right()
inside the flatTap ... then a subsequent map wouldn't be operating on "a-string" it would be operating on whatever the R value was before the flatTap call?
so in that instance it does actually ignore what i'm returning from flatTap as long as its not an error
b
Typical use-case would be something like this:
Copy code
def processClaim(c: Claim): IO<ClaimCheck> = TODO()

processClaim(claim)
  .flatTap(claimCheck -> IO(<http://LOG.info|LOG.info>("Claim check: $claimCheck")))
  .flatMap(... more IO operations here operating on the claim check...)
+/- the syntax on IO(LOG(...)))
@tim that is correct
t
yep okay that makes sense to me
thank you!!! 🙏
this is sooo cool :)
🙌🏼 1
💯 1