This is more of a general fp-question, but this se...
# arrow
y
This is more of a general fp-question, but this seems like a good place to ask it. Why does Applicative exist instead of just apply? This might sound weird, but can't
just
be implemented for any Functor by using an instance of that functor and calling map on it with a function that disregards it's results and just returns whatever value was passed in. So something like this
Copy code
inline fun <reified F,A,B> Kind<F,A>.Companion.just(value: B): Kind<F,B> {
    return getSomeStaticValueForFunctor<F>().map { B }
}
So instead of having Applicative, couldn't there be just a
FunctorWithStaticInstance
that then
Apply
inherits from (and by extension
Monad
also inherits from). Additonally, the docs mention that some classes can implement
Apply
but not
Applicative
, but isn't it guaranteed that every typeclass in general will have an instance? so then with a pretty simple implementation you could probably produce a
Functor<Unit>
that will be a
val
on
FunctorWithStaticInstance
. Or maybe even every functor could just have a static instance by default. Maybe then you could even have
just
on
Functor
itself. I'm sure that there's definitely something wrong with my reasoning, but I honestly cannot figure it out.
r
Hi Youssef. The Applicative vs Apply abstraction is rather human made. In practice Apply or Applicative in Arrow serve more purposes than
just
for example all the mapN and product variations. You could reencode as you mention but that would form in fact an Apply or Applicative since
just
is required for types to be constructed and that is in essence what apply can do. The static factory would replace that but it does not change the semantics of what you are doing. There may be types that are just functor and have no apply though rare and for the same reasoning we could have invented a type class called
Just
with it’s laws. Whether a type provides a value statically or a new instance through a constructor is rather a detail that IMO FP largely ignores, it’s still the “just” constructor in disguise.
Also type classes are grouped by convenience but in most of them you can break them apart to come up with weaker abstractions that are still lawful
To be clear there is not a straight up relationship between type-classes conveniently encoded and their CT concepts that is one to one. So it’s up to us to see if these abstractions can be further broken or unified to our convenience since we aim to be pragmatic. There is also a potential re-encoding of type classes as effect handlers @Jannis and I were discussing and we may propose for Arrow since that would give us support in suspend with better syntax; so nothing it’s written in stone :)
y
Yeah I see what you mean. I was kinda confused when I saw that some classes can implement Apply but not Applicative, but I guess the reasoning behind that is just an abstraction sort of thing (i.e. kinda like how you can't get values out of IO in Haskell because IO doesn't allow you to do that, it only allows the runtime to do it. I was probably thinking more in terms of what the classes actually will look like, but I can see the fallacy with that because technically speaking a Functor wouldn't work if you don't have values of it, but it's not really about the reality of the situation, it's more about the abstraction that that typeclasss brings and the rules of the class that implements it.
@raulraja encoding typeclasses as effect handlers does sound really interesting, though I honestly can't imagine how that would look like lol
r
Well, credit goes to @Jannis who wrote some of these examples but given Continuations in Kotlin gives us the same power as something like ContT in Haskell but optimized and we have the reset / shift primitives that allow to build any algebra an effect handler can be as simple as:
Copy code
interface Raise<E> {
  suspend fun raise(e: E): Nothing
}
We have eliminated
F
in the type class since
suspend
has all the power of traveling between Fs through the continuation callback and implementors of
raise
can just
shift { Left(err) }
for example in the case of an Either instance
That is: Suspension eliminates F or polymorphism bound to a generic since that power of going between types already exists in the Continuation monad.
y
Yeah I did take a look at the arrow delimited continuations and while I didn't necessarily understand how it works the power that it brings does seem really incredible when compared to the previous generics approach. It's honestly quite incredible how you all were able to navigate around all this complex implementation details to make this work, especially because at the same time it is performant and idiomatic.
Hopefully Kotlin will become more mainstream in the FP community especially with the power that Arrow brings
r
For what is worth we are past that point given the monthly downloads and many companies using Arrow even without a stable release so we are just hopping for better times all around. I get some people are frustrated because of the unstables APIs but doing this right in Kotlin has taken over 4 years and has required also the lang to evolve alongside for these things to be possible. Commiting to a 1.0 stable in a lesser and scala/haskell like encoding is not good for Arrow or anyone. We are gonna get there and hopefully in the most ergonomic way possible.
❤️ 1
The good news is that we are almost there. 1.4.0 is already in Arrow thanks to @Rachel and meta is ready to start the stable release encoding proposal.
🎉 1