Skip to content

Instantly share code, notes, and snippets.

@JorgeCastilloPrz
Last active May 23, 2020 08:16
Show Gist options
  • Save JorgeCastilloPrz/c0a4604b9a5dedc89be82b13cfcc1315 to your computer and use it in GitHub Desktop.
Save JorgeCastilloPrz/c0a4604b9a5dedc89be82b13cfcc1315 to your computer and use it in GitHub Desktop.
PolymorphicProgram.kt
package me.jorgecastillo.polymorphicapps.polymorphic
import arrow.Kind
import arrow.core.Option
import arrow.core.left
import arrow.core.right
import arrow.effects.IO
import arrow.effects.async
import arrow.effects.fix
import arrow.effects.typeclasses.Async
import arrow.typeclasses.ApplicativeError
data class UserId(val value: String)
data class User(val userId: UserId)
data class Task(val value: String)
sealed class UserLookupError : RuntimeException() //assuming you are using exceptions
data class UserNotInLocalStorage(val user: User) : UserLookupError()
data class UserNotInRemoteStorage(val user: User) : UserLookupError()
data class UnknownError(val underlying: Throwable) : UserLookupError()
interface DataSource<F> {
fun allTasksByUser(user: User): Kind<F, List<Task>>
}
class LocalDataSource<F>(A: ApplicativeError<F, Throwable>) : DataSource<F>, ApplicativeError<F, Throwable> by A {
private val localCache: Map<User, List<Task>> =
mapOf(User(UserId("user1")) to listOf(Task("LocalTask assigned to user1")))
override fun allTasksByUser(user: User): Kind<F, List<Task>> =
Option.fromNullable(localCache[user]).fold(
{ raiseError(UserNotInLocalStorage(user)) },
{ just(it) }
)
}
class RemoteDataSource<F>(A: Async<F>) : DataSource<F>, Async<F> by A {
private val internetStorage: Map<User, List<Task>> =
mapOf(User(UserId("user2")) to listOf(Task("Remote Task assigned to user2")))
override fun allTasksByUser(user: User): Kind<F, List<Task>> =
async { cb ->
//allows you to take values from callbacks and place them back in the context of `F`
Option.fromNullable(internetStorage[user]).fold(
{ cb(UserNotInRemoteStorage(user).left()) },
{ cb(it.right()) }
)
}
}
class TaskRepository<F>(
private val localDS: DataSource<F>,
private val remoteDS: RemoteDataSource<F>,
AE: ApplicativeError<F, Throwable>) : ApplicativeError<F, Throwable> by AE {
fun allTasksByUser(user: User): Kind<F, List<Task>> =
localDS.allTasksByUser(user).handleErrorWith {
when (it) {
is UserNotInLocalStorage -> remoteDS.allTasksByUser(user)
else -> raiseError(UnknownError(it))
}
}
}
class Module<F>(A: Async<F>) {
private val localDataSource: LocalDataSource<F> = LocalDataSource(A)
private val remoteDataSource: RemoteDataSource<F> = RemoteDataSource(A)
val repository: TaskRepository<F> = TaskRepository(localDataSource, remoteDataSource, A)
}
object test {
@JvmStatic
fun main(args: Array<String>): Unit {
val user1 = User(UserId("user1"))
val user2 = User(UserId("user2"))
val user3 = User(UserId("unknown user"))
val singleModule = Module(SingleK.async())
singleModule.run {
repository.allTasksByUser(user1).fix().single.subscribe({ println(it) }, { println(it) })
repository.allTasksByUser(user2).fix().single.subscribe({ println(it) }, { println(it) })
repository.allTasksByUser(user3).fix().single.subscribe({ println(it) }, { println(it) })
}
val maybeModule = Module(MaybeK.async())
maybeModule.run {
repository.allTasksByUser(user1).fix().maybe.subscribe({ println(it) }, { println(it) })
repository.allTasksByUser(user2).fix().maybe.subscribe({ println(it) }, { println(it) })
repository.allTasksByUser(user3).fix().maybe.subscribe({ println(it) }, { println(it) })
}
val observableModule = Module(ObservableK.async())
observableModule.run {
repository.allTasksByUser(user1).fix().observable.subscribe { println(it) }
repository.allTasksByUser(user2).fix().observable.subscribe { println(it) }
repository.allTasksByUser(user3).fix().observable.subscribe({ println(it) }, { println(it) })
}
val flowableModule = Module(FlowableK.async())
flowableModule.run {
repository.allTasksByUser(user1).fix().flowable.subscribe { println(it) }
repository.allTasksByUser(user2).fix().flowable.subscribe { println(it) }
repository.allTasksByUser(user3).fix().flowable.subscribe({ println(it) }, { println(it) })
}
val deferredModule = Module(DeferredK.async())
deferredModule.run {
runBlocking {
try {
println(repository.allTasksByUser(user1).fix().deferred.await())
println(repository.allTasksByUser(user2).fix().deferred.await())
println(repository.allTasksByUser(user3).fix().deferred.await())
} catch (e: UserNotInRemoteStorage) {
println(e)
}
}
}
val ioModule = Module(IO.async())
ioModule.run {
println(repository.allTasksByUser(user1).fix().attempt().unsafeRunSync())
println(repository.allTasksByUser(user2).fix().attempt().unsafeRunSync())
println(repository.allTasksByUser(user3).fix().attempt().unsafeRunSync())
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment