Hello! I'm trying to understand how to mark side e...
# arrow
s
Hello! I'm trying to understand how to mark side effects correctly. Documentation to Arrow Fx describes using
suspend () -> A
instead of
IO<A>
. Does this literally means that signature of the function that produces a side-effect should be
suspend () -> A
, or it can be
suspend (parameter1, parameter2...) -> A
? Which one is correct and why:
Copy code
data class Message(val content: String)
suspend fun sideEffectProducer(message: Message) = println(message)
or
Copy code
data class Message(val content: String)
suspend fun Message.sideEffectProducer() = println(this)
And a follow-up question - how functions with side effects differ from pure functions that use
either
block and
.bind()
results? Both of them are
suspend
👍 1
s
Hey @Sergei Zubov, In the Arrow Fx Documentation when we refer to
suspend () -> A
we talk about
suspend fun f(): A
, or
val f: suspend () -> A
since both are the same. They're just 2 different ways to describe the same thing, and you can move back and forth between the two different notations easily. (For example using method referencing). So both example you shared is correct and depends on personal preference and code styles used. I personally almost always use
suspend fun example(): Unit
and when I need to pass it somewhere as a lambda I reference it as a method reference
?let(::example)
.
either { }
has two different versions: •
either.eager
which works doesn't allow suspension. So you cannot call any other
suspend
fun inside. •
either { }
which does allow suspension, so you can call other suspending functions like
withContext
inside. So using
either.eager
you can only write pure functions since you cannot call
suspend
inside.
either { }
on the other hand, does allow suspension, so we can call side-effecting code from inside. It's similar to
List#map
. Since it's
inline
it allows for suspension to go through, but depending on if you call a suspending function inside then the result is considered not-pure.
🙏 2
s
Thank you, Simon. So, in general, it goes down to a "Mark functions with side effects as `suspend`" rule. No matter of style - function with parameters, extension function or any other thing 😀 But shouldn't I also make all of my functions
suspend
to make my program run in a declarative deferred way? Or as far as higher-level function is
suspend
, calling non-suspend function from it is fine?
s
But shouldn't I also make all of my functions 
suspend
 to make my program run in a declarative deferred way?
Yes, that's very typical. You could say that marking all your functions with suspend allows you to run your program in a declarative deferred way. You could also that's the result of being referential-transparent and purity. These are all properties we care about in FP, and so you could also use different arguments for this. They all achieve the same goal I think, which is being able to reason about code in small blocks.
Or as far as higher-level function is 
suspend
, calling non-suspend function from it is fine?
Calling pure code from side-effecting code is always fine. So calling non-suspend functions from
suspend
fun is fine 👍 Actually, I always try to make as many non-suspend functions as possible. I am thinking now specifically about domain mappers, computations, combining data, ... etc.
👍 1
For example:
Copy code
suspend fun fetchUser(id: Int): User = ...
suspend fun fetchProfile(id: Int): Profile = ...
suspend fun fetchAvatar(id: Int): Avatar = ...

fun createUserProfile(user: User, profile: Profile, avatar: Avatar): UserProfile = ...

suspend fun fetchUserProfile(id: Int): UserProfile =
  createUserProfile(fetchUser(id), fetchProfile(id), fetchAvatar(id))
If later I want to refactor my network calls to return
Either<Throwable, A>
my code changes.
Copy code
suspend fun fetchUserProfile(id: Int): UserProfile =
  fetchUser(id).zip(fetchProfile(id), fetchAvatar(id), ::createUserProfile)
🙌 1
s
Yes, domain mappers are exactly what I am dealing with right now😄 Thank you
👍 1
s
You're very welcome @Sergei Zubov! 🙌
🚀 1
100 Views