Tower Guidev2
05/27/2021, 5:13 PMJannis
05/27/2021, 5:34 PMIO = suspend
and Either
for modeling errors. To be clear, this is simply not equally powerful as higher kinded types in kotlin and there is a lot this cannot do, however it is easier to use and sufficient for most.
There are two common uses for higher kinds:
• monad stacks for effects: This is replaced by suspend
functions and comprehensions, however atm it is not equally powerful because kotlin continuations cannot be resumed multiple times and thus we can only model short-circuit-like monads with effects (such as either
or any nullable type). IO
is equal to suspend
in arrow terms.
• Abstracting over "containers" of values. This is simply not possible without kinds, suspend functions or not. If your use case falls under this category, you are better off rolling your own kind implementation 😕 But with arrow-meta this may very well come back at some point, but for now its simply not there...Jannis
05/27/2021, 5:35 PMEither
. Kinds may or may not come back, but not in the form as before and certainly not without compiler pluginsraulraja
05/27/2021, 5:38 PMF
or transformer stacks for the case of IO which is what F is most of the time. It’s also much faster because the compiler optimizes suspend programs over your traditional IO based ones. Even with kinds, since in Kotlin they are emulated, partially applying type arguments of kinds like Either and downcasting with fix
is painful and something we don’t want to proliferate as idiom in Arrow.Tower Guidev2
05/28/2021, 8:28 AMInput
and Output
, what I require is to define that Input1
can only produce Output1
, and Input2
can only produce Output2
etc.. in addition I also need to define the data type contained in each Input
and Output
, so that Input1
can only contain a String
, and Output1
can only contain a Long
(I am only using these simple data types for example purposes, in reality I would use Complex custom data classes
) , wouldnt Higher Kinded Types solve this for me?, I am lost as to develop another solution that can both define a strict releationship between my Input & Output types and the data they contain.
I need to be able to define that Input1 can only ever contain DataTypeA and only ever produce Output1; That Output1 can only ever contain DataTypeB.
I wish to define an entire "family" of Input(s) and Output(s) that have the above restrictions.
Is that possible in Kotlin?raulraja
05/28/2021, 12:43 PMraulraja
05/28/2021, 12:43 PMraulraja
05/28/2021, 12:44 PMTower Guidev2
05/28/2021, 1:39 PMval action: Action = LoginAction(username, password)
viewmodel.react(action).collect {
reaction ->
when (reaction) {
LoginReaction -> println("\t\t ${reaction.reaction.output}")
Reaction1 -> println("\t\t ${reaction.reaction.output}")
Reaction2 -> println("\t\t ${reaction.reaction.output}")
Reaction3 -> println("\t\t ${reaction.reaction.output}")
}
}
In my viewmodel the react function I would have
fun react(action: Action): Flow<Reaction> = flow {
val reaction: Reaction = when (action) {
is LoginAction -> {
Val accessCode = callLoginApi(it.password, it.username)
LoginReaction(accessCode)
}
is ActionOne -> {
Reaction1(42L)
}
is ActionTwo -> {
Reaction2(CustomOutput(frank = 24L, homer = "Help!"))
}
is ActionThree -> {
Reaction3(CustomOutputTwo(geezer = 2442L, su = "Why"))
}
else -> TODO()
}
emit(reaction)
}
raulraja
05/28/2021, 5:30 PMTower Guidev2
06/01/2021, 9:30 AMType mismatch: inferred type is LoginReaction but Reaction<{A & LoginAction}> was expected
, when splitting your solution to fit how
I wish to design my Android App.
My issue is that each Android Activity will create multiple Action(s) and collect the related Reaction(s)
I've attempted to refactor your code as follows:-
In My Android ViewModel class I have created these two functions
fun <A : Action> react(action: A): Flow<*> = flow {
val reaction = when (action) {
is LoginAction -> {
val accessToken : String = LoginApi.login(action.password, action.password) // This is an Asynchronous RESTful API call
react(action) { LoginReaction(accessToken) } // COMPILE ERROR HERE!!!!!!!!!!!!
}
else -> TODO()
}
emit(reaction)
}
/**
* This is the motherload. This function ensures that a given [Action] [A]
* creates a [Flow] of [Reaction] for [R], a Reaction that only accepts actions of type [A]
*/
private inline fun <X : Action, Y : Reaction<X>> react(action: X, crossinline function: (X) -> Y): Reaction<X> {
return function(action)
}
I call my public react fucntion from my activity:-
val loginAction = LoginAction(UserName("test"), Password("test"))
viewmodel.react(loginAction).collectLatest {
println("debug $it")
}
where has my lack of Kotlin expertise let me down? 🤔raulraja
06/01/2021, 10:04 AMTower Guidev2
06/01/2021, 10:20 AMraulraja
06/01/2021, 10:51 AMTower Guidev2
06/01/2021, 10:57 AMraulraja
06/01/2021, 11:09 AMimport kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
class Aardvark {
inline suspend fun <A : Action> react(action: A): Flow<Reaction<*>> =
when (action) {
is LoginAction -> react(action) { LoginReaction("UUID.randomUUID().toString()") }
is ActionOne -> react(action) { ReactionOne("UUID.randomUUID().toString()") }
else -> TODO()
}
inline suspend fun <X : Action, Y : Reaction<X>> react(action: X, crossinline function: (X) -> Y): Flow<Y> =
flow { emit(function(action)) }
}
raulraja
06/01/2021, 11:13 AMY
of Reaction in its type argument and inference then knows Y
above has to unify as a Reaction<*>
. Since Y
is declared as Reaction<X>
and *
in kotlin means DONT_CARE
then it unifies properly.
If you try to decompose in cases you will have to manually cast like in your case if you want to make it compile because Kotlin does not make an effort to unify generic type bounds with sealed hierarchies.raulraja
06/01/2021, 11:15 AMA : Action
should work with just Action
no type args since A is not reified or used in a projection in the return typeraulraja
06/01/2021, 11:16 AMTower Guidev2
06/01/2021, 12:20 PMraulraja
06/01/2021, 12:23 PMjulian
06/01/2021, 3:01 PMKotlin does not make an effort to unify generic type bounds with sealed hierarchies
julian
06/01/2021, 3:20 PMfun react(action: Action): Flow<Reaction> = flow {
val reaction: Reaction = when (action) {
is LoginAction -> {
Val accessCode = callLoginApi(it.password, it.username)
LoginReaction(accessCode)
}
is ActionOne -> {
Reaction1(42L)
}
is ActionTwo -> {
Reaction2(CustomOutput(frank = 24L, homer = "Help!"))
}
is ActionThree -> {
Reaction3(CustomOutputTwo(geezer = 2442L, su = "Why"))
}
else -> TODO()
}
emit(reaction)
}
since the consumer of the flow is going to have to when-is
on the Reaction
s in the flow, either way.raulraja
06/01/2021, 4:53 PMsealed interface Exp<A>
data class NumberExp(val value: Int): Exp<Int>
data class StringExp(val value: String): Exp<String>
sealed interface Codec<A>
object IntCodec: Codec<Int>
object StringCodec: Codec<String>
fun <A> cantUnifyA(expr: Exp<A>): Codec<A> =
when (expr) {
is NumberExp -> IntCodec //Type mismatch: inferred type is IntCodec but Codec<A> was expected
is StringExp -> StringCodec //Type mismatch: inferred type is StringCodec but Codec<A> was expected
}
raulraja
06/01/2021, 4:55 PMreact(action) { reaction }
to create its flows all individuallyraulraja
06/01/2021, 4:56 PMraulraja
06/01/2021, 4:56 PMaction.react()
which on the base class would have returned Reaction<A>
where A is always matched by the members instead of using pattern matching.raulraja
06/01/2021, 5:18 PMsealed interface Exp<A> {
fun codec(): Codec<A>
}
data class NumberExp(val value: Int): Exp<Int> {
override fun codec(): Codec<Int> = IntCodec
}
data class StringExp(val value: String): Exp<String> {
override fun codec(): Codec<String> = StringCodec
}
sealed interface Codec<A>
object IntCodec: Codec<Int>
object StringCodec: Codec<String>
fun <A> canUnifyA(expr: Exp<A>): Codec<A> =
expr.codec()
raulraja
06/01/2021, 5:18 PMraulraja
06/01/2021, 5:23 PMCodec<A>
refers to the same A
as Expr<A>
julian
06/01/2021, 5:28 PMraulraja
06/01/2021, 5:28 PM