marc0der
07/23/2020, 12:18 PMApplicative
and Traverse
. An example of product would be when translating the following function from Scala:
def compose[G[_]](G: Applicative[G]): Applicative[({type f[x] = F[G[x]]})#f] = {
val self = this
new Applicative[({type f[x] = F[G[x]]})#f] {
def unit[A](a: => A) = self.unit(G.unit(a))
override def map2[A,B,C](fga: F[G[A]], fgb: F[G[B]])(f: (A,B) => C) =
self.map2(fga, fgb)(G.map2(_,_)(f))
}
}
The problem here being how to express that type lambda ({type f[x] = F[G[x]]})#f
. The unit
and map2
functions should return types like (F[A], G[A])
, translated to Kotlin that would be Tuple2<Kind<F, A>, Kind<G, A>>
. From what I can tell, this simply isn't possible in Kotlin, but maybe someone else can see a way of achieving this that I just can't see.Jannis
07/23/2020, 12:50 PMApplicative
will look more like this:
fun just(a: A): Kind<F, Kind<G, A>>
fun Kind<F, Kind<G, A>>.map2(fgb: Kind<F, Kind<G, B>>, f: (A, B) -> C): Kind<F, Kind<G, A>>
This allows us to write an instance like this:
class Composed<F, G, A>(val v: Kind<F, Kind<G, A>>): Kind<ForComposed, Kind<F, Kind<G, A>>>
instance ComposedApplicative<F, G> : Applicative<Kind<ForComposed, Kind<F, G>>> {
fun AF(): Applicative<F> // require F to be applicative
fun AG(): Applicative<G> // require G to be applicative
override fun <A> just(a: A): Composed<F, G> = Composed(AF().just(AG().just(a)))
override fun <A, B, C> Composed<F, G, B>.map2(fgb: Composed<F, G, B>, f: (A, B) -> C): Composed<F, G, C> = AF().run {
AG().run {
v.map2(fgb.v) { ga, gb -> ga.map2(gb, f) }
}
}
}
Now admittedly this is not as nice as the scala version, and we don't even have this exact code in arrow atm. We are currently using a different type called Nested
to create a Kind<Kind<F, G>, A>
instead of using something like Composed<F, G, A>
and make that higherkinded.
This makes stuff a bit uglier, but avoids wrapping and unwrapping.
For now this is all in this file: https://github.com/arrow-kt/arrow-incubator/blob/master/arrow-mtl-data/src/main/kotlin/arrow/mtl/typeclasses/Composed.kt
I will at some point add the code that I have written here to arrow, but probably not before we have actual newtypes so that the Composed
wrapper gets removed at runtime.Jannis
07/23/2020, 12:52 PMmarc0der
07/23/2020, 1:26 PMproduct
function that has the return types I mentioned:
def product[G[_]](G: Applicative[G]): Applicative[({type f[x] = (F[x], G[x])})#f] = {
val self = this
new Applicative[({type f[x] = (F[x], G[x])})#f] {
def unit[A](a: => A): (F[A], G[A]) = (self.unit(a), G.unit(a))
override def apply[A,B](fs: (F[A => B], G[A => B]))(p: (F[A], G[A])): (F[B], G[B]) =
(self.apply(fs._1)(p._1), G.apply(fs._2)(p._2))
}
}
This has a return type of (F[x], G[x])
as I mentioned. The type lambda here being ({type f[x] = (F[x], G[x])})#f
marc0der
07/23/2020, 1:31 PMJannis
07/23/2020, 1:34 PM@higherkind
class Product<F, G, A>(val v: Tuple2<Kind<F, A>, Kind<G, A>>)
// now we can define
interface ProductApplicative<F, G> : Applicative<Kind<ForProduct, Kind<F, G>>> {
// the implementation is easy now because we can go back and forth between Product and Tuple2 easily
}
Tbh I don't really like the scala encoding here, newtyping for instance resolution seems much better to me and there is lots of examples of this in the haskell worldmarc0der
07/23/2020, 1:41 PMmarc0der
07/24/2020, 3:55 PMProduct
higher kind as shim:
@higherkind
class Product<F, G, A>(val value: Pair<Kind<F, A>, Kind<G, A>>) : ProductOf<F, G, A>
I have the following function for the product of two applicatives:
fun <F, G> product(
af: Applicative<F>,
ag: Applicative<G>
): Applicative<ProductPartialOf<F, G>> =
object : Applicative<ProductPartialOf<F, G>> {
override fun <A, B> apply(
fgab: ProductOf<F, G, (A) -> B>,
fga: ProductOf<F, G, A>
): ProductOf<F, G, B> {
val (fab: Kind<F, (A) -> B>, gab: Kind<G, (A) -> B>) = fgab.fix().value
val (fa: Kind<F, A>, ga: Kind<G, A>) = fga.fix().value
return Product(Pair(af.apply(fab, fa), ag.apply(gab, ga)))
}
override fun <A> unit(a: A): ProductOf<F, G, A> =
Product(Pair(af.unit(a), ag.unit(a)))
}
This does deviate a little from your approach, but I wanted something that didn't require the fabrication of a ProductApplicative
interface for the sake of simplicity of the exercise. Do you think that this will fly?Jannis
07/24/2020, 4:15 PMIdMonad<A> : Monad<ForId>, IdApplicative<A>
, but for composed instances like this it does not really matter 🙂