Skip to content

Instantly share code, notes, and snippets.

@npatarino
Forked from raulraja/dstagless.kt
Created May 22, 2018 23:14
Show Gist options
  • Save npatarino/c853a345064db0ef29cfb5b367636cdb to your computer and use it in GitHub Desktop.
Save npatarino/c853a345064db0ef29cfb5b367636cdb to your computer and use it in GitHub Desktop.
Tagless data source strategies with Arrow
import arrow.Kind
import arrow.core.Option
import arrow.core.left
import arrow.core.right
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>>
}
interface Repository<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 asssigned 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 DefaultRepository<F>(
val localDS: DataSource<F>,
val remoteDS: RemoteDataSource<F>,
AE: ApplicativeError<F, Throwable>) : Repository<F>, ApplicativeError<F, Throwable> by AE {
override 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>(val A: Async<F>) {
val localDataSource: LocalDataSource<F> = LocalDataSource(A)
val remoteDataSource: RemoteDataSource<F> = RemoteDataSource(A)
val repository: Repository<F> = DefaultRepository(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 ioModule = Module(IO.async())
ioModule.run {
println(repository.allTasksByUser(user1).fix().attempt().unsafeRunSync())
//Right(b=[Task(value=LocalTask asssigned to user1)])
println(repository.allTasksByUser(user2).fix().attempt().unsafeRunSync())
//Right(b=[Task(value=Remote Task assigned to user2)])
println(repository.allTasksByUser(user3).fix().attempt().unsafeRunSync())
//Left(a=UserNotInRemoteStorage(user=User(userId=UserId(value=unknown user))))
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment