From 48b5da82f2eba149952f03f197e83c3a05c0bd6d Mon Sep 17 00:00:00 2001 From: Jorge Castillo Date: Fri, 6 Oct 2017 12:56:45 +0200 Subject: [PATCH 01/19] add free monads module as a copy of tagless final one --- free-monads/.gitignore | 1 + free-monads/build.gradle | 83 +++++++++++++ free-monads/proguard-rules.pro | 21 ++++ .../ExampleInstrumentedTest.java | 24 ++++ free-monads/src/main/AndroidManifest.xml | 25 ++++ .../kotlinandroid/context/GetHeroesContext.kt | 28 +++++ .../kotlinandroid/data/HeroesRepository.kt | 40 +++++++ .../data/datasource/HeroesDataSource.kt | 8 ++ .../data/datasource/remote/DataMappers.kt | 6 + .../remote/MarvelNetworkDataSource.kt | 71 +++++++++++ .../datasource/remote/SubHeroesDataSource.kt | 13 ++ .../domain/model/CharacterError.kt | 21 ++++ .../kotlinandroid/domain/model/SuperHero.kt | 3 + .../domain/usecase/HeroesUseCases.kt | 22 ++++ .../functional/AsyncResultMonadControl.kt | 111 ++++++++++++++++++ .../kotlinandroid/functional/Future.kt | 53 +++++++++ .../presentation/SuperHeroesPresentation.kt | 73 ++++++++++++ .../presentation/navigation/Navigation.kt | 15 +++ .../view/SuperHeroDetailActivity.kt | 68 +++++++++++ .../view/SuperHeroListActivity.kt | 65 ++++++++++ .../view/adapter/HeroesCardAdapter.kt | 38 ++++++ .../kotlinandroid/view/extensions.kt | 8 ++ .../view/viewmodel/SuperHeroViewModel.kt | 3 + .../src/main/res/layout/activity_detail.xml | 75 ++++++++++++ .../src/main/res/layout/activity_main.xml | 17 +++ free-monads/src/main/res/layout/item_hero.xml | 12 ++ .../src/main/res/layout/item_hero_card.xml | 18 +++ .../kotlinandroid/ExampleUnitTest.java | 16 +++ settings.gradle | 2 +- 29 files changed, 939 insertions(+), 1 deletion(-) create mode 100644 free-monads/.gitignore create mode 100644 free-monads/build.gradle create mode 100644 free-monads/proguard-rules.pro create mode 100644 free-monads/src/androidTest/java/com/github/jorgecastillo/kotlinandroid/ExampleInstrumentedTest.java create mode 100644 free-monads/src/main/AndroidManifest.xml create mode 100644 free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/context/GetHeroesContext.kt create mode 100644 free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/data/HeroesRepository.kt create mode 100644 free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/data/datasource/HeroesDataSource.kt create mode 100644 free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/data/datasource/remote/DataMappers.kt create mode 100644 free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/data/datasource/remote/MarvelNetworkDataSource.kt create mode 100644 free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/data/datasource/remote/SubHeroesDataSource.kt create mode 100644 free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/domain/model/CharacterError.kt create mode 100644 free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/domain/model/SuperHero.kt create mode 100644 free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/domain/usecase/HeroesUseCases.kt create mode 100644 free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/functional/AsyncResultMonadControl.kt create mode 100644 free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/functional/Future.kt create mode 100644 free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/presentation/SuperHeroesPresentation.kt create mode 100644 free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/presentation/navigation/Navigation.kt create mode 100644 free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/view/SuperHeroDetailActivity.kt create mode 100644 free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/view/SuperHeroListActivity.kt create mode 100644 free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/view/adapter/HeroesCardAdapter.kt create mode 100644 free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/view/extensions.kt create mode 100644 free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/view/viewmodel/SuperHeroViewModel.kt create mode 100644 free-monads/src/main/res/layout/activity_detail.xml create mode 100644 free-monads/src/main/res/layout/activity_main.xml create mode 100644 free-monads/src/main/res/layout/item_hero.xml create mode 100644 free-monads/src/main/res/layout/item_hero_card.xml create mode 100644 free-monads/src/test/java/com/github/jorgecastillo/kotlinandroid/ExampleUnitTest.java diff --git a/free-monads/.gitignore b/free-monads/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/free-monads/.gitignore @@ -0,0 +1 @@ +/build diff --git a/free-monads/build.gradle b/free-monads/build.gradle new file mode 100644 index 0000000..020cfe0 --- /dev/null +++ b/free-monads/build.gradle @@ -0,0 +1,83 @@ +buildscript { + repositories { + jcenter() + mavenCentral() + maven { url "https://plugins.gradle.org/m2/" } + maven { url 'https://maven.fabric.io/public' } + } + + dependencies { + classpath "gradle.plugin.io.gitlab.arturbosch.detekt:detekt-gradle-plugin:1.0.0.M11" + } +} + +apply plugin: 'kotlin-kapt' +apply from: rootProject.file('gradle/generated-kotlin-sources.gradle') +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-android-extensions' +apply plugin: 'io.gitlab.arturbosch.detekt' + +android { + compileSdkVersion compileVersion + buildToolsVersion build_tools_version + + defaultConfig { + applicationId appId + minSdkVersion minSdk + targetSdkVersion targetSdk + versionCode version_code + versionName version_name + testInstrumentationRunner testInstrumentationRunner + + buildConfigField "String", "MARVEL_PUBLIC_KEY", marvelPublicKey + buildConfigField "String", "MARVEL_PRIVATE_KEY", marvelPrivateKey + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } + + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + debug.java.srcDirs += 'build/generated/source/kaptKotlin/debug' + release.java.srcDirs += 'build/generated/source/kaptKotlin/release' + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_7 + targetCompatibility JavaVersion.VERSION_1_7 + } + + lintOptions { + abortOnError false + } +} + +detekt { + version = "1.0.0.M11" + input = "$project.projectDir.absolutePath" + config = "$project.rootDir/detekt.yml" + filters = ".*test.*,.*/resources/.*,.*/tmp/.*" + output = "$project.projectDir.absolutePath/reports/" + report = "$project.projectDir.absolutePath/reports/" +} + +dependencies { + compile project(':shared') + + kapt "io.kategory:kategory-annotations-processor:$kategory_version" + compile "com.android.support:appcompat-v7:$supportLibrary" + compile "com.android.support:design:$supportLibrary" + compile "com.android.support:cardview-v7:$supportLibrary" + compile "com.android.support:recyclerview-v7:$supportLibrary" + compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" + compile "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion" + compile "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutinesVersion" + compile "com.squareup.picasso:picasso:2.5.2" + compile "io.kategory:kategory-annotations:$kategory_version" + compile "io.kategory:kategory:$kategory_version" + compile 'com.karumi:marvelapiclient:1.0.1' +} diff --git a/free-monads/proguard-rules.pro b/free-monads/proguard-rules.pro new file mode 100644 index 0000000..f1b4245 --- /dev/null +++ b/free-monads/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/free-monads/src/androidTest/java/com/github/jorgecastillo/kotlinandroid/ExampleInstrumentedTest.java b/free-monads/src/androidTest/java/com/github/jorgecastillo/kotlinandroid/ExampleInstrumentedTest.java new file mode 100644 index 0000000..850ffdc --- /dev/null +++ b/free-monads/src/androidTest/java/com/github/jorgecastillo/kotlinandroid/ExampleInstrumentedTest.java @@ -0,0 +1,24 @@ +package com.github.jorgecastillo.kotlinandroid; + +import android.content.Context; +import android.support.test.InstrumentationRegistry; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.*; + +/** + * Instrumented test, which will execute on an Android device. + * + * @see Testing documentation + */ +@RunWith(AndroidJUnit4.class) public class ExampleInstrumentedTest { + @Test public void useAppContext() throws Exception { + // Context of the app under test. + Context appContext = InstrumentationRegistry.getTargetContext(); + + assertEquals("com.github.jorgecastillo.free_monads", appContext.getPackageName()); + } +} diff --git a/free-monads/src/main/AndroidManifest.xml b/free-monads/src/main/AndroidManifest.xml new file mode 100644 index 0000000..d338c30 --- /dev/null +++ b/free-monads/src/main/AndroidManifest.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + diff --git a/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/context/GetHeroesContext.kt b/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/context/GetHeroesContext.kt new file mode 100644 index 0000000..897a2f5 --- /dev/null +++ b/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/context/GetHeroesContext.kt @@ -0,0 +1,28 @@ +package com.github.jorgecastillo.kotlinandroid.di.context + +import android.content.Context +import com.github.jorgecastillo.kotlinandroid.BuildConfig +import com.github.jorgecastillo.kotlinandroid.presentation.SuperHeroDetailView +import com.github.jorgecastillo.kotlinandroid.presentation.SuperHeroesListView +import com.github.jorgecastillo.kotlinandroid.presentation.SuperHeroesView +import com.github.jorgecastillo.kotlinandroid.presentation.navigation.HeroDetailsPage +import com.karumi.marvelapiclient.CharacterApiClient +import com.karumi.marvelapiclient.MarvelApiConfig.Builder + +sealed class SuperHeroesContext() { + + abstract val ctx: Context + abstract val view: SuperHeroesView + + val heroDetailsPage = HeroDetailsPage() + val apiClient + get() = CharacterApiClient(Builder( + BuildConfig.MARVEL_PUBLIC_KEY, + BuildConfig.MARVEL_PRIVATE_KEY).debug().build()) + + data class GetHeroesContext(override val ctx: Context, override val view: SuperHeroesListView) : SuperHeroesContext() + data class GetHeroDetailsContext(override val ctx: Context, + override val view: SuperHeroDetailView) : SuperHeroesContext() +} + + diff --git a/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/data/HeroesRepository.kt b/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/data/HeroesRepository.kt new file mode 100644 index 0000000..d27fd5c --- /dev/null +++ b/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/data/HeroesRepository.kt @@ -0,0 +1,40 @@ +package com.github.jorgecastillo.kotlinandroid.data + +import com.github.jorgecastillo.kotlinandroid.data.CachePolicy.LocalFirst +import com.github.jorgecastillo.kotlinandroid.data.CachePolicy.LocalOnly +import com.github.jorgecastillo.kotlinandroid.data.CachePolicy.NetworkFirst +import com.github.jorgecastillo.kotlinandroid.data.CachePolicy.NetworkOnly +import com.github.jorgecastillo.kotlinandroid.data.datasource.remote.fetchAllHeroes +import com.github.jorgecastillo.kotlinandroid.data.datasource.remote.fetchHeroDetails +import com.github.jorgecastillo.kotlinandroid.data.datasource.remote.fetchHeroesFromAvengerComics +import com.karumi.marvelapiclient.model.CharacterDto +import kategory.HK + +sealed class CachePolicy { + object NetworkOnly : CachePolicy() + object NetworkFirst : CachePolicy() + object LocalOnly : CachePolicy() + object LocalFirst : CachePolicy() +} + +inline fun getHeroesWithCachePolicy(policy: CachePolicy): HK> = when (policy) { + is NetworkOnly -> fetchAllHeroes() + is NetworkFirst -> fetchAllHeroes() // TODO change to conditional call + is LocalOnly -> fetchAllHeroes() // TODO change to local only cache call + is LocalFirst -> fetchAllHeroes() // TODO change to conditional call +} + +inline fun getHeroDetails(policy: CachePolicy, heroId: String): HK = when (policy) { + is NetworkOnly -> fetchHeroDetails(heroId) + is NetworkFirst -> fetchHeroDetails(heroId) // TODO change to conditional call + is LocalOnly -> fetchHeroDetails(heroId) // TODO change to local only cache call + is LocalFirst -> fetchHeroDetails(heroId) // TODO change to conditional call +} + +inline fun getHeroesFromAvengerComicsWithCachePolicy(policy: CachePolicy): HK> = + when (policy) { + is NetworkOnly -> fetchHeroesFromAvengerComics() + is NetworkFirst -> fetchHeroesFromAvengerComics() // TODO change to conditional call + is LocalOnly -> fetchHeroesFromAvengerComics() // TODO change to local only cache call + is LocalFirst -> fetchHeroesFromAvengerComics() // TODO change to conditional call + } diff --git a/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/data/datasource/HeroesDataSource.kt b/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/data/datasource/HeroesDataSource.kt new file mode 100644 index 0000000..681ac04 --- /dev/null +++ b/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/data/datasource/HeroesDataSource.kt @@ -0,0 +1,8 @@ +package com.github.jorgecastillo.kotlinandroid.data.datasource + +import com.github.jorgecastillo.kotlinandroid.domain.model.SuperHero + +interface HeroesDataSource { + + fun getAll(): List +} diff --git a/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/data/datasource/remote/DataMappers.kt b/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/data/datasource/remote/DataMappers.kt new file mode 100644 index 0000000..5dabeb4 --- /dev/null +++ b/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/data/datasource/remote/DataMappers.kt @@ -0,0 +1,6 @@ +package com.github.jorgecastillo.kotlinandroid.data.datasource.remote + +import com.github.jorgecastillo.kotlinandroid.domain.model.SuperHero +import com.karumi.marvelapiclient.model.CharacterDto + +fun mapApiCharacterToSuperHero(character: CharacterDto) = SuperHero(character.name) diff --git a/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/data/datasource/remote/MarvelNetworkDataSource.kt b/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/data/datasource/remote/MarvelNetworkDataSource.kt new file mode 100644 index 0000000..57de8d3 --- /dev/null +++ b/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/data/datasource/remote/MarvelNetworkDataSource.kt @@ -0,0 +1,71 @@ +package com.github.jorgecastillo.kotlinandroid.data.datasource.remote + +import com.github.jorgecastillo.kotlinandroid.di.context.SuperHeroesContext.GetHeroDetailsContext +import com.github.jorgecastillo.kotlinandroid.di.context.SuperHeroesContext.GetHeroesContext +import com.github.jorgecastillo.kotlinandroid.domain.model.CharacterError +import com.github.jorgecastillo.kotlinandroid.domain.model.CharacterError.AuthenticationError +import com.github.jorgecastillo.kotlinandroid.domain.model.CharacterError.NotFoundError +import com.github.jorgecastillo.kotlinandroid.domain.model.CharacterError.UnknownServerError +import com.github.jorgecastillo.kotlinandroid.functional.MonadControl +import com.github.jorgecastillo.kotlinandroid.functional.monadControl +import com.karumi.marvelapiclient.MarvelApiException +import com.karumi.marvelapiclient.MarvelAuthApiException +import com.karumi.marvelapiclient.model.CharacterDto +import com.karumi.marvelapiclient.model.CharactersQuery +import kategory.HK +import kategory.Option +import kategory.binding +import java.net.HttpURLConnection + +/* + * This is the network data source. Calls are made using Karumi's MarvelApiClient. + * @see "https://github.com/Karumi/MarvelApiClientAndroid" + * + * Both requests return a new Reader enclosing an action to resolve when you provide them with the + * required execution context. + * + * The getHeroesFromAvengerComicsUseCase() method maps the fetchAllHeroes() result to filter the list with just the + * elements with given conditions. It's returning heroes appearing on comics with the "Avenger" + * word in the title. Yep, I wanted to retrieve Avengers but the Marvel API is a bit weird + * sometimes. + */ +fun exceptionAsCharacterError(e: Throwable): CharacterError = + when (e) { + is MarvelAuthApiException -> AuthenticationError + is MarvelApiException -> + if (e.httpCode == HttpURLConnection.HTTP_NOT_FOUND) NotFoundError + else UnknownServerError(Option.Some(e)) + else -> UnknownServerError((Option.Some(e))) + } + + +inline fun fetchAllHeroes( + C: MonadControl = monadControl()): HK> = + C.binding { + val query = CharactersQuery.Builder.create().withOffset(0).withLimit(50).build() + val ctx = C.ask().bind() + C.catch( + { ctx.apiClient.getAll(query).response.characters }, + { exceptionAsCharacterError(it) } + ) + } + +inline fun fetchHeroDetails(heroId: String, + C: MonadControl = monadControl()): HK = + C.binding { + val ctx = C.ask().bind() + C.catch( + { ctx.apiClient.getCharacter(heroId).response }, + { exceptionAsCharacterError(it) } + ) + } + +inline fun fetchHeroesFromAvengerComics( + C: MonadControl = monadControl()): HK> = + C.map(fetchAllHeroes(C), { + it.filter { + it.comics.items.map { it.name }.filter { + it.contains("Avenger", true) + }.count() > 0 + } + }) diff --git a/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/data/datasource/remote/SubHeroesDataSource.kt b/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/data/datasource/remote/SubHeroesDataSource.kt new file mode 100644 index 0000000..d3fde3a --- /dev/null +++ b/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/data/datasource/remote/SubHeroesDataSource.kt @@ -0,0 +1,13 @@ +package com.github.jorgecastillo.kotlinandroid.data.datasource.remote + +import com.github.jorgecastillo.kotlinandroid.data.datasource.HeroesDataSource +import com.github.jorgecastillo.kotlinandroid.domain.model.SuperHero + +class StubHeroesDataSource : HeroesDataSource { + + override fun getAll() = listOf(SuperHero("LambdaMan"), SuperHero("AlfredoLambda"), SuperHero("IronMan"), SuperHero("Spider-Man"), + SuperHero("Batman"), SuperHero("Goku"), SuperHero("Vegeta"), SuperHero("SuperMan"), + SuperHero("Ant-Man"), SuperHero("Krilin"), SuperHero("Super Mario"), SuperHero("Wolverine"), + SuperHero("Massacre"), SuperHero("Jake Wharton"), SuperHero("Jesus Christ"), + SuperHero("Donald Trump (villain)")) +} diff --git a/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/domain/model/CharacterError.kt b/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/domain/model/CharacterError.kt new file mode 100644 index 0000000..8c93036 --- /dev/null +++ b/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/domain/model/CharacterError.kt @@ -0,0 +1,21 @@ +package com.github.jorgecastillo.kotlinandroid.domain.model + +import kategory.* + +/** + * This sealed class represents all the possible errors that the app is going to model inside its + * domain. All the exceptions / errors provoked by third party libraries or APIs are mapped to any + * of the types defined on this class. + * + * Mapping exceptions to errors allows the domain use case functions to be referentially + * transparent, which means that they are completely clear and straightforward about what they + * return just by reading their public function output types. + * + * Other approaches like exceptions + callback propagation (to be able to surpass thread limits) + * bring not required complexity to the architecture introducing asynchronous semantics. + */ +sealed class CharacterError { + object AuthenticationError : CharacterError() + object NotFoundError : CharacterError() + data class UnknownServerError(val e: Option = Option.None) : CharacterError() +} diff --git a/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/domain/model/SuperHero.kt b/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/domain/model/SuperHero.kt new file mode 100644 index 0000000..cd6acff --- /dev/null +++ b/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/domain/model/SuperHero.kt @@ -0,0 +1,3 @@ +package com.github.jorgecastillo.kotlinandroid.domain.model + +data class SuperHero(val name: String) diff --git a/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/domain/usecase/HeroesUseCases.kt b/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/domain/usecase/HeroesUseCases.kt new file mode 100644 index 0000000..ded951c --- /dev/null +++ b/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/domain/usecase/HeroesUseCases.kt @@ -0,0 +1,22 @@ +package com.github.jorgecastillo.kotlinandroid.domain.usecase + +import com.github.jorgecastillo.kotlinandroid.data.CachePolicy.NetworkOnly +import com.github.jorgecastillo.kotlinandroid.data.getHeroDetails +import com.github.jorgecastillo.kotlinandroid.data.getHeroesFromAvengerComicsWithCachePolicy +import com.github.jorgecastillo.kotlinandroid.data.getHeroesWithCachePolicy +import com.github.jorgecastillo.kotlinandroid.di.context.SuperHeroesContext.GetHeroDetailsContext +import com.github.jorgecastillo.kotlinandroid.di.context.SuperHeroesContext.GetHeroesContext +import com.github.jorgecastillo.kotlinandroid.domain.model.CharacterError +import com.github.jorgecastillo.kotlinandroid.functional.MonadControl +import com.github.jorgecastillo.kotlinandroid.functional.monadControl +import com.karumi.marvelapiclient.model.CharacterDto +import kategory.HK + +inline fun getHeroesUseCase(): HK> = + getHeroesWithCachePolicy(NetworkOnly) + +inline fun getHeroDetailsUseCase(heroId: String): HK = + getHeroDetails(NetworkOnly, heroId) + +inline fun getHeroesFromAvengerComicsUseCase(): HK> = + getHeroesFromAvengerComicsWithCachePolicy(NetworkOnly) diff --git a/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/functional/AsyncResultMonadControl.kt b/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/functional/AsyncResultMonadControl.kt new file mode 100644 index 0000000..eecdefb --- /dev/null +++ b/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/functional/AsyncResultMonadControl.kt @@ -0,0 +1,111 @@ +package com.github.jorgecastillo.kotlinandroid.functional + +import com.github.jorgecastillo.kotlinandroid.di.context.SuperHeroesContext +import com.github.jorgecastillo.kotlinandroid.di.context.SuperHeroesContext.GetHeroDetailsContext +import com.github.jorgecastillo.kotlinandroid.di.context.SuperHeroesContext.GetHeroesContext +import com.github.jorgecastillo.kotlinandroid.domain.model.CharacterError +import kategory.Either +import kategory.EitherT +import kategory.EitherTKindPartial +import kategory.HK +import kategory.Kleisli +import kategory.KleisliMonadErrorInstance +import kategory.KleisliMonadErrorInstanceImplicits +import kategory.KleisliMonadReaderInstance +import kategory.KleisliMonadReaderInstanceImplicits +import kategory.MonadError +import kategory.MonadReader +import kategory.Tuple2 +import kategory.Typeclass +import kategory.andThen +import kategory.instance + +class AsyncResultHK private constructor() +typealias AsyncResultKind = kategory.HK2 +typealias AsyncResultKindPartial = kategory.HK + +@Suppress("UNCHECKED_CAST") +inline fun AsyncResultKind.ev(): AsyncResult = + this as AsyncResult + +typealias Result = Kleisli, D, A> + +class AsyncResult( + val value: Result) : AsyncResultKind { + + fun run(ctx: D): HK, A> = value.run(ctx) + + companion object { + inline operator fun invoke(): MonadControl, D, CharacterError> = monadControl() + } +} + +inline fun monadControl(): MonadControl = + if (D::class == GetHeroesContext::class) { + AsyncResultMonadControl.Companion.getHeroesControl() as MonadControl + } else { + AsyncResultMonadControl.Companion.getHeroDetailsControl() as MonadControl + } + +interface MonadControl : + MonadError, + MonadReader, + Typeclass + +interface AsyncResultMonadControl : MonadControl, D, CharacterError> { + + companion object { + } + + fun ETME(): MonadError, CharacterError> = + EitherT.monadError() + + fun KME(): KleisliMonadErrorInstance, D, CharacterError> = + KleisliMonadErrorInstanceImplicits.instance(ETME()) + + fun KMR(): KleisliMonadReaderInstance, D> = + KleisliMonadReaderInstanceImplicits.instance(ETME()) + + override fun map(fa: HK, A>, f: (A) -> B): AsyncResult { + return AsyncResult(KME().map(fa.ev().value, f)) + } + + override fun product(fa: HK, A>, + fb: HK, B>): AsyncResult> { + return AsyncResult(KME().product(fa.ev().value, fb.ev().value)) + } + + override fun flatMap(fa: HK, A>, + f: (A) -> HK, B>): AsyncResult { + return AsyncResult(KME().flatMap(fa.ev().value, f.andThen { it.ev().value })) + } + + override fun handleErrorWith(fa: HK, A>, + f: (CharacterError) -> HK, A>): AsyncResult { + return AsyncResult(KME().handleErrorWith(fa.ev().value, f.andThen { it.ev().value })) + } + + override fun tailRecM(a: A, + f: (A) -> HK, Either>): AsyncResult { + return AsyncResult(KME().tailRecM(a, f.andThen { it.ev().value })) + } + + override fun raiseError(e: CharacterError): AsyncResult = + AsyncResult(KME().raiseError(e)) + + override fun pure(a: A): AsyncResult = + AsyncResult(KME().pure(a)) + + override fun ask(): AsyncResult = + AsyncResult(KMR().ask()) + + override fun local(f: (D) -> D, fa: HK, A>): AsyncResult { + return AsyncResult(KMR().local(f, fa.ev().value)) + } +} + +@instance(AsyncResultMonadControl::class) +interface GetHeroesControlAsyncResultMonadControlInstance : AsyncResultMonadControl + +@instance(AsyncResultMonadControl::class) +interface GetHeroDetailsControlAsyncResultMonadControlInstance : AsyncResultMonadControl diff --git a/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/functional/Future.kt b/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/functional/Future.kt new file mode 100644 index 0000000..ac53056 --- /dev/null +++ b/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/functional/Future.kt @@ -0,0 +1,53 @@ +package com.github.jorgecastillo.kotlinandroid.functional + +import kategory.* +import kotlinx.coroutines.experimental.CommonPool +import kotlinx.coroutines.experimental.Deferred +import kotlinx.coroutines.experimental.android.UI +import kotlinx.coroutines.experimental.async +import kotlinx.coroutines.experimental.launch + +/** + * Basic future implementation to achieve asynchronicity based on Kotlin coroutines. + */ +@higherkind +@deriving(Functor::class, Applicative::class, Monad::class) +class Future : FutureKind { + + private val deferred: Deferred + + private constructor(deferred: Deferred) { + this.deferred = deferred + } + + constructor(f: () -> T) : this(async(CommonPool) { f() }) + + fun map(f: (T) -> X): Future = Future(async(CommonPool) { f(deferred.await()) }) + + fun flatMap(f: (T) -> FutureKind): Future = + Future(async(CommonPool) { f(deferred.await()).ev().deferred.await() }) + + fun ap(ff: FutureKind<(T) -> B>): Future = + zip(ff).map { it.b(it.a) } + + fun zip(fb: FutureKind): Future> = + flatMap { a -> fb.ev().map { b -> Tuple2(a, b) } } + + fun onComplete(f: (T) -> Unit) { + launch(UI) { + f(deferred.await()) + } + } + + companion object { + + fun tailRecM(a: A, f: (A) -> FutureKind>): Future = + f(a).ev().flatMap { + it.fold({ tailRecM(a, f).ev() }, { Future.pure(it) }) + } + + fun pure(a: A): Future = + Future({ a }) + + } +} diff --git a/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/presentation/SuperHeroesPresentation.kt b/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/presentation/SuperHeroesPresentation.kt new file mode 100644 index 0000000..5366e91 --- /dev/null +++ b/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/presentation/SuperHeroesPresentation.kt @@ -0,0 +1,73 @@ +package com.github.jorgecastillo.kotlinandroid.presentation + +import com.github.jorgecastillo.kotlinandroid.di.context.SuperHeroesContext +import com.github.jorgecastillo.kotlinandroid.di.context.SuperHeroesContext.GetHeroDetailsContext +import com.github.jorgecastillo.kotlinandroid.di.context.SuperHeroesContext.GetHeroesContext +import com.github.jorgecastillo.kotlinandroid.domain.model.CharacterError +import com.github.jorgecastillo.kotlinandroid.domain.model.CharacterError.AuthenticationError +import com.github.jorgecastillo.kotlinandroid.domain.model.CharacterError.NotFoundError +import com.github.jorgecastillo.kotlinandroid.domain.model.CharacterError.UnknownServerError +import com.github.jorgecastillo.kotlinandroid.domain.usecase.getHeroDetailsUseCase +import com.github.jorgecastillo.kotlinandroid.domain.usecase.getHeroesUseCase +import com.github.jorgecastillo.kotlinandroid.functional.MonadControl +import com.github.jorgecastillo.kotlinandroid.functional.monadControl +import com.github.jorgecastillo.kotlinandroid.view.viewmodel.SuperHeroViewModel +import com.karumi.marvelapiclient.model.CharacterDto +import com.karumi.marvelapiclient.model.MarvelImage +import kategory.HK +import kategory.binding +import kategory.flatMap + +interface SuperHeroesView { + fun showNotFoundError() + fun showGenericError() + fun showAuthenticationError() +} + +interface SuperHeroesListView : SuperHeroesView { + fun drawHeroes(heroes: List) +} + +interface SuperHeroDetailView : SuperHeroesView { + fun drawHero(hero: SuperHeroViewModel) +} + +inline fun onHeroListItemClick(heroId: String, C: MonadControl) = + C.ask().flatMap(C, { + it.heroDetailsPage.go(heroId, C) + }) + +fun displayErrors(ctx: SuperHeroesContext, c: CharacterError): Unit { + when (c) { + is NotFoundError -> ctx.view.showNotFoundError() + is UnknownServerError -> ctx.view.showGenericError() + is AuthenticationError -> ctx.view.showAuthenticationError() + } +} + +inline fun getSuperHeroes(C: MonadControl = monadControl()): HK = + C.binding { + val ctx = C.ask().bind() + val result = C.handleError(getHeroesUseCase(), { displayErrors(ctx, it); emptyList() }).bind() + ctx.view.drawHeroes(result.map { + SuperHeroViewModel( + it.id, + it.name, + it.thumbnail.getImageUrl(MarvelImage.Size.PORTRAIT_UNCANNY), + it.description) + }) + C.pure(Unit) + } + +inline fun getSuperHeroDetails(heroId: String, + C: MonadControl = monadControl()): HK = + C.binding { + val ctx = C.ask().bind() + val result = C.handleError(getHeroDetailsUseCase(heroId), { displayErrors(ctx, it); CharacterDto() }).bind() + ctx.view.drawHero(SuperHeroViewModel( + result.id, + result.name, + result.thumbnail.getImageUrl(MarvelImage.Size.PORTRAIT_UNCANNY), + result.description)) + C.pure(Unit) + } diff --git a/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/presentation/navigation/Navigation.kt b/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/presentation/navigation/Navigation.kt new file mode 100644 index 0000000..72491b4 --- /dev/null +++ b/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/presentation/navigation/Navigation.kt @@ -0,0 +1,15 @@ +package com.github.jorgecastillo.kotlinandroid.presentation.navigation + +import com.github.jorgecastillo.kotlinandroid.di.context.SuperHeroesContext.GetHeroesContext +import com.github.jorgecastillo.kotlinandroid.domain.model.CharacterError +import com.github.jorgecastillo.kotlinandroid.functional.MonadControl +import com.github.jorgecastillo.kotlinandroid.view.SuperHeroDetailActivity +import kategory.map + +class HeroDetailsPage { + + inline fun go(heroId: String, C: MonadControl) = + C.ask().map(C, { + SuperHeroDetailActivity.launch(it.ctx, heroId) + }) +} diff --git a/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/view/SuperHeroDetailActivity.kt b/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/view/SuperHeroDetailActivity.kt new file mode 100644 index 0000000..2c62a81 --- /dev/null +++ b/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/view/SuperHeroDetailActivity.kt @@ -0,0 +1,68 @@ +package com.github.jorgecastillo.kotlinandroid.view + +import android.content.Context +import android.content.Intent +import android.os.Bundle +import android.support.design.widget.Snackbar +import android.support.v7.app.AppCompatActivity +import android.widget.Toast +import com.github.jorgecastillo.kotlinandroid.R +import com.github.jorgecastillo.kotlinandroid.R.string +import com.github.jorgecastillo.kotlinandroid.di.context.SuperHeroesContext.GetHeroDetailsContext +import com.github.jorgecastillo.kotlinandroid.functional.AsyncResult +import com.github.jorgecastillo.kotlinandroid.functional.ev +import com.github.jorgecastillo.kotlinandroid.presentation.SuperHeroDetailView +import com.github.jorgecastillo.kotlinandroid.presentation.getSuperHeroDetails +import com.github.jorgecastillo.kotlinandroid.view.viewmodel.SuperHeroViewModel +import kotlinx.android.synthetic.main.activity_detail.appBar +import kotlinx.android.synthetic.main.activity_detail.collapsingToolbar +import kotlinx.android.synthetic.main.activity_detail.description +import kotlinx.android.synthetic.main.activity_detail.headerImage + +class SuperHeroDetailActivity : AppCompatActivity(), SuperHeroDetailView { + + companion object { + val EXTRA_HERO_ID = "EXTRA_HERO_ID" + + fun launch(source: Context, heroId: String) { + val intent = Intent(source, SuperHeroDetailActivity::class.java) + intent.putExtra(EXTRA_HERO_ID, heroId) + source.startActivity(intent) + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_detail) + } + + override fun onResume() { + super.onResume() + intent.extras?.let { + val heroId = it.getString(EXTRA_HERO_ID) + getSuperHeroDetails(heroId, AsyncResult()).ev().run(GetHeroDetailsContext(this, this)) + } ?: closeWithError() + } + + private fun closeWithError() { + Toast.makeText(this, string.hero_id_needed, Toast.LENGTH_SHORT).show() + } + + override fun drawHero(hero: SuperHeroViewModel) { + collapsingToolbar.title = hero.name + description.text = hero.description.let { if (it.isNotEmpty()) it else getString(string.empty_description) } + headerImage.loadImageAsync(hero.photoUrl) + } + + override fun showNotFoundError() { + Snackbar.make(appBar, string.not_found, Snackbar.LENGTH_SHORT).show() + } + + override fun showGenericError() { + Snackbar.make(appBar, string.generic, Snackbar.LENGTH_SHORT).show() + } + + override fun showAuthenticationError() { + Snackbar.make(appBar, string.authentication, Snackbar.LENGTH_SHORT).show() + } +} diff --git a/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/view/SuperHeroListActivity.kt b/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/view/SuperHeroListActivity.kt new file mode 100644 index 0000000..2b1cd9f --- /dev/null +++ b/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/view/SuperHeroListActivity.kt @@ -0,0 +1,65 @@ +package com.github.jorgecastillo.kotlinandroid.view + +import android.os.Bundle +import android.support.design.widget.Snackbar +import android.support.v7.app.AppCompatActivity +import android.support.v7.widget.LinearLayoutManager +import com.github.jorgecastillo.kotlinandroid.R +import com.github.jorgecastillo.kotlinandroid.di.context.SuperHeroesContext.GetHeroesContext +import com.github.jorgecastillo.kotlinandroid.functional.AsyncResult +import com.github.jorgecastillo.kotlinandroid.functional.ev +import com.github.jorgecastillo.kotlinandroid.functional.monadControl +import com.github.jorgecastillo.kotlinandroid.presentation.SuperHeroesListView +import com.github.jorgecastillo.kotlinandroid.presentation.getSuperHeroes +import com.github.jorgecastillo.kotlinandroid.presentation.onHeroListItemClick +import com.github.jorgecastillo.kotlinandroid.view.adapter.HeroesCardAdapter +import com.github.jorgecastillo.kotlinandroid.view.viewmodel.SuperHeroViewModel +import kotlinx.android.synthetic.main.activity_main.heroesList + +class SuperHeroListActivity : AppCompatActivity(), SuperHeroesListView { + + private lateinit var adapter: HeroesCardAdapter + private lateinit var heroesContext: GetHeroesContext + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_main) + setupDependencyGraph() + setupList() + } + + private fun setupDependencyGraph() { + heroesContext = GetHeroesContext(this, this) + } + + private fun setupList() { + heroesList.setHasFixedSize(true) + heroesList.layoutManager = LinearLayoutManager(this) + adapter = HeroesCardAdapter(itemClick = { + onHeroListItemClick(it.heroId, AsyncResult()).ev().run(heroesContext) + }) + heroesList.adapter = adapter + } + + override fun onResume() { + super.onResume() + getSuperHeroes(AsyncResult()).ev().run(GetHeroesContext(this, this)) + } + + override fun drawHeroes(heroes: List) = runOnUiThread { + adapter.characters = heroes + adapter.notifyDataSetChanged() + } + + override fun showNotFoundError() = runOnUiThread { + Snackbar.make(heroesList, R.string.not_found, Snackbar.LENGTH_SHORT).show() + } + + override fun showGenericError() = runOnUiThread { + Snackbar.make(heroesList, R.string.generic, Snackbar.LENGTH_SHORT).show() + } + + override fun showAuthenticationError() = runOnUiThread { + Snackbar.make(heroesList, R.string.authentication, Snackbar.LENGTH_SHORT).show() + } +} diff --git a/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/view/adapter/HeroesCardAdapter.kt b/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/view/adapter/HeroesCardAdapter.kt new file mode 100644 index 0000000..50b9336 --- /dev/null +++ b/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/view/adapter/HeroesCardAdapter.kt @@ -0,0 +1,38 @@ +package com.github.jorgecastillo.kotlinandroid.view.adapter + +import android.support.v7.widget.RecyclerView +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import com.github.jorgecastillo.kotlinandroid.R +import com.github.jorgecastillo.kotlinandroid.view.viewmodel.SuperHeroViewModel +import com.squareup.picasso.Picasso +import kotlinx.android.synthetic.main.item_hero_card.view.* + +class HeroesCardAdapter( + var characters: List = ArrayList(), + val itemClick: (SuperHeroViewModel) -> Unit) : RecyclerView.Adapter() { + + override fun onCreateViewHolder(parent: ViewGroup, pos: Int): ViewHolder { + val view = LayoutInflater.from(parent.context).inflate(R.layout.item_hero_card, parent, false) + return ViewHolder(view, itemClick) + } + + override fun onBindViewHolder(holder: ViewHolder, pos: Int) { + holder.bind(characters[pos]) + } + + override fun getItemCount() = characters.size + + class ViewHolder(view: View, + val itemClick: (SuperHeroViewModel) -> Unit) : RecyclerView.ViewHolder( + view) { + + fun bind(hero: SuperHeroViewModel) { + with(hero) { + Picasso.with(itemView.context).load(photoUrl).into(itemView.picture) + itemView.setOnClickListener { itemClick(this) } + } + } + } +} diff --git a/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/view/extensions.kt b/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/view/extensions.kt new file mode 100644 index 0000000..45f8ff4 --- /dev/null +++ b/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/view/extensions.kt @@ -0,0 +1,8 @@ +package com.github.jorgecastillo.kotlinandroid.view + +import android.widget.ImageView +import com.squareup.picasso.Picasso + +fun ImageView.loadImageAsync(url: String) { + Picasso.with(context).load(url).into(this) +} diff --git a/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/view/viewmodel/SuperHeroViewModel.kt b/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/view/viewmodel/SuperHeroViewModel.kt new file mode 100644 index 0000000..e28708f --- /dev/null +++ b/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/view/viewmodel/SuperHeroViewModel.kt @@ -0,0 +1,3 @@ +package com.github.jorgecastillo.kotlinandroid.view.viewmodel + +data class SuperHeroViewModel(val heroId: String, val name: String, val photoUrl: String, val description: String) diff --git a/free-monads/src/main/res/layout/activity_detail.xml b/free-monads/src/main/res/layout/activity_detail.xml new file mode 100644 index 0000000..dc69ee7 --- /dev/null +++ b/free-monads/src/main/res/layout/activity_detail.xml @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + + + diff --git a/free-monads/src/main/res/layout/activity_main.xml b/free-monads/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..e4ca0d6 --- /dev/null +++ b/free-monads/src/main/res/layout/activity_main.xml @@ -0,0 +1,17 @@ + + + + + diff --git a/free-monads/src/main/res/layout/item_hero.xml b/free-monads/src/main/res/layout/item_hero.xml new file mode 100644 index 0000000..5578300 --- /dev/null +++ b/free-monads/src/main/res/layout/item_hero.xml @@ -0,0 +1,12 @@ + + diff --git a/free-monads/src/main/res/layout/item_hero_card.xml b/free-monads/src/main/res/layout/item_hero_card.xml new file mode 100644 index 0000000..a08cc14 --- /dev/null +++ b/free-monads/src/main/res/layout/item_hero_card.xml @@ -0,0 +1,18 @@ + + + + + diff --git a/free-monads/src/test/java/com/github/jorgecastillo/kotlinandroid/ExampleUnitTest.java b/free-monads/src/test/java/com/github/jorgecastillo/kotlinandroid/ExampleUnitTest.java new file mode 100644 index 0000000..0bcebf3 --- /dev/null +++ b/free-monads/src/test/java/com/github/jorgecastillo/kotlinandroid/ExampleUnitTest.java @@ -0,0 +1,16 @@ +package com.github.jorgecastillo.kotlinandroid; + +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * Example local unit test, which will execute on the development machine (host). + * + * @see Testing documentation + */ +public class ExampleUnitTest { + @Test public void addition_isCorrect() throws Exception { + assertEquals(4, 2 + 2); + } +} diff --git a/settings.gradle b/settings.gradle index da483ed..0bde4e5 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1 @@ -include ':nested-monads', ':tagless-final', ':monad-transformers', ':shared' +include ':nested-monads', ':tagless-final', ':monad-transformers', ':shared', ':free-monads' From 5af3c98233d613219c4121ccb462a475cb44efab Mon Sep 17 00:00:00 2001 From: Jorge Castillo Date: Fri, 6 Oct 2017 12:58:38 +0200 Subject: [PATCH 02/19] remove some unused classes --- .../data/datasource/HeroesDataSource.kt | 8 -------- .../data/datasource/remote/SubHeroesDataSource.kt | 13 ------------- .../data/datasource/HeroesDataSource.kt | 8 -------- .../data/datasource/remote/DataMappers.kt | 6 ------ .../data/datasource/remote/SubHeroesDataSource.kt | 13 ------------- 5 files changed, 48 deletions(-) delete mode 100644 free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/data/datasource/HeroesDataSource.kt delete mode 100644 free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/data/datasource/remote/SubHeroesDataSource.kt delete mode 100644 tagless-final/src/main/java/com/github/jorgecastillo/kotlinandroid/data/datasource/HeroesDataSource.kt delete mode 100644 tagless-final/src/main/java/com/github/jorgecastillo/kotlinandroid/data/datasource/remote/DataMappers.kt delete mode 100644 tagless-final/src/main/java/com/github/jorgecastillo/kotlinandroid/data/datasource/remote/SubHeroesDataSource.kt diff --git a/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/data/datasource/HeroesDataSource.kt b/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/data/datasource/HeroesDataSource.kt deleted file mode 100644 index 681ac04..0000000 --- a/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/data/datasource/HeroesDataSource.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.github.jorgecastillo.kotlinandroid.data.datasource - -import com.github.jorgecastillo.kotlinandroid.domain.model.SuperHero - -interface HeroesDataSource { - - fun getAll(): List -} diff --git a/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/data/datasource/remote/SubHeroesDataSource.kt b/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/data/datasource/remote/SubHeroesDataSource.kt deleted file mode 100644 index d3fde3a..0000000 --- a/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/data/datasource/remote/SubHeroesDataSource.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.github.jorgecastillo.kotlinandroid.data.datasource.remote - -import com.github.jorgecastillo.kotlinandroid.data.datasource.HeroesDataSource -import com.github.jorgecastillo.kotlinandroid.domain.model.SuperHero - -class StubHeroesDataSource : HeroesDataSource { - - override fun getAll() = listOf(SuperHero("LambdaMan"), SuperHero("AlfredoLambda"), SuperHero("IronMan"), SuperHero("Spider-Man"), - SuperHero("Batman"), SuperHero("Goku"), SuperHero("Vegeta"), SuperHero("SuperMan"), - SuperHero("Ant-Man"), SuperHero("Krilin"), SuperHero("Super Mario"), SuperHero("Wolverine"), - SuperHero("Massacre"), SuperHero("Jake Wharton"), SuperHero("Jesus Christ"), - SuperHero("Donald Trump (villain)")) -} diff --git a/tagless-final/src/main/java/com/github/jorgecastillo/kotlinandroid/data/datasource/HeroesDataSource.kt b/tagless-final/src/main/java/com/github/jorgecastillo/kotlinandroid/data/datasource/HeroesDataSource.kt deleted file mode 100644 index 681ac04..0000000 --- a/tagless-final/src/main/java/com/github/jorgecastillo/kotlinandroid/data/datasource/HeroesDataSource.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.github.jorgecastillo.kotlinandroid.data.datasource - -import com.github.jorgecastillo.kotlinandroid.domain.model.SuperHero - -interface HeroesDataSource { - - fun getAll(): List -} diff --git a/tagless-final/src/main/java/com/github/jorgecastillo/kotlinandroid/data/datasource/remote/DataMappers.kt b/tagless-final/src/main/java/com/github/jorgecastillo/kotlinandroid/data/datasource/remote/DataMappers.kt deleted file mode 100644 index 5dabeb4..0000000 --- a/tagless-final/src/main/java/com/github/jorgecastillo/kotlinandroid/data/datasource/remote/DataMappers.kt +++ /dev/null @@ -1,6 +0,0 @@ -package com.github.jorgecastillo.kotlinandroid.data.datasource.remote - -import com.github.jorgecastillo.kotlinandroid.domain.model.SuperHero -import com.karumi.marvelapiclient.model.CharacterDto - -fun mapApiCharacterToSuperHero(character: CharacterDto) = SuperHero(character.name) diff --git a/tagless-final/src/main/java/com/github/jorgecastillo/kotlinandroid/data/datasource/remote/SubHeroesDataSource.kt b/tagless-final/src/main/java/com/github/jorgecastillo/kotlinandroid/data/datasource/remote/SubHeroesDataSource.kt deleted file mode 100644 index d3fde3a..0000000 --- a/tagless-final/src/main/java/com/github/jorgecastillo/kotlinandroid/data/datasource/remote/SubHeroesDataSource.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.github.jorgecastillo.kotlinandroid.data.datasource.remote - -import com.github.jorgecastillo.kotlinandroid.data.datasource.HeroesDataSource -import com.github.jorgecastillo.kotlinandroid.domain.model.SuperHero - -class StubHeroesDataSource : HeroesDataSource { - - override fun getAll() = listOf(SuperHero("LambdaMan"), SuperHero("AlfredoLambda"), SuperHero("IronMan"), SuperHero("Spider-Man"), - SuperHero("Batman"), SuperHero("Goku"), SuperHero("Vegeta"), SuperHero("SuperMan"), - SuperHero("Ant-Man"), SuperHero("Krilin"), SuperHero("Super Mario"), SuperHero("Wolverine"), - SuperHero("Massacre"), SuperHero("Jake Wharton"), SuperHero("Jesus Christ"), - SuperHero("Donald Trump (villain)")) -} From b5cdc7119521cc1e648f6ae50634c4ae36f0012e Mon Sep 17 00:00:00 2001 From: Jorge Castillo Date: Fri, 6 Oct 2017 17:54:05 +0200 Subject: [PATCH 03/19] add algebra and interpreter to data layer --- .../kotlinandroid/data/HeroesRepository.kt | 17 +- .../kotlinandroid/data/algebra/algebras.kt | 55 ++++++ .../data/datasource/remote/DataMappers.kt | 6 - .../remote/MarvelNetworkDataSource.kt | 71 ------- .../AsyncResultHeroesDataSourceInterpreter.kt | 79 ++++++++ .../kotlinandroid/functional/AsyncResult.kt | 173 ++++++++++++++++++ .../functional/AsyncResultMonadControl.kt | 111 ----------- .../kotlinandroid/functional/Future.kt | 48 ++--- 8 files changed, 341 insertions(+), 219 deletions(-) create mode 100644 free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/data/algebra/algebras.kt delete mode 100644 free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/data/datasource/remote/DataMappers.kt delete mode 100644 free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/data/datasource/remote/MarvelNetworkDataSource.kt create mode 100644 free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/data/interpreter/AsyncResultHeroesDataSourceInterpreter.kt create mode 100644 free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/functional/AsyncResult.kt delete mode 100644 free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/functional/AsyncResultMonadControl.kt diff --git a/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/data/HeroesRepository.kt b/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/data/HeroesRepository.kt index d27fd5c..064d5f0 100644 --- a/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/data/HeroesRepository.kt +++ b/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/data/HeroesRepository.kt @@ -4,9 +4,9 @@ import com.github.jorgecastillo.kotlinandroid.data.CachePolicy.LocalFirst import com.github.jorgecastillo.kotlinandroid.data.CachePolicy.LocalOnly import com.github.jorgecastillo.kotlinandroid.data.CachePolicy.NetworkFirst import com.github.jorgecastillo.kotlinandroid.data.CachePolicy.NetworkOnly -import com.github.jorgecastillo.kotlinandroid.data.datasource.remote.fetchAllHeroes -import com.github.jorgecastillo.kotlinandroid.data.datasource.remote.fetchHeroDetails -import com.github.jorgecastillo.kotlinandroid.data.datasource.remote.fetchHeroesFromAvengerComics +import com.github.jorgecastillo.kotlinandroid.data.interpreter.fetchAllHeroes +import com.github.jorgecastillo.kotlinandroid.data.interpreter.getHeroDetails +import com.github.jorgecastillo.kotlinandroid.data.interpreter.fetchHeroesFromAvengerComics import com.karumi.marvelapiclient.model.CharacterDto import kategory.HK @@ -25,10 +25,13 @@ inline fun getHeroesWithCachePolicy(policy: CachePolicy): HK getHeroDetails(policy: CachePolicy, heroId: String): HK = when (policy) { - is NetworkOnly -> fetchHeroDetails(heroId) - is NetworkFirst -> fetchHeroDetails(heroId) // TODO change to conditional call - is LocalOnly -> fetchHeroDetails(heroId) // TODO change to local only cache call - is LocalFirst -> fetchHeroDetails(heroId) // TODO change to conditional call + is NetworkOnly -> getHeroDetails(heroId) + is NetworkFirst -> getHeroDetails( + heroId) // TODO change to conditional call + is LocalOnly -> getHeroDetails( + heroId) // TODO change to local only cache call + is LocalFirst -> getHeroDetails( + heroId) // TODO change to conditional call } inline fun getHeroesFromAvengerComicsWithCachePolicy(policy: CachePolicy): HK> = diff --git a/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/data/algebra/algebras.kt b/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/data/algebra/algebras.kt new file mode 100644 index 0000000..4ee25d0 --- /dev/null +++ b/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/data/algebra/algebras.kt @@ -0,0 +1,55 @@ +package com.github.jorgecastillo.kotlinandroid.data.algebra + +import com.karumi.marvelapiclient.model.CharacterDto +import kategory.Free +import kategory.FreeMonadInstance +import kategory.FunctionK +import kategory.HK +import kategory.Monad +import kategory.foldMap +import kategory.higherkind +import kategory.map +import kategory.monad + +/** + * Algebra for Hero data sources. Algebras are defined by a sealed class (ADT) with a limited amount of implementations reflecting the operations available. + */ +@higherkind sealed class HeroesDataSourceAlgebra : HeroesDataSourceAlgebraKind { + + class GetAll : HeroesDataSourceAlgebra>() + class GetSingle(val heroId: String) : HeroesDataSourceAlgebra>() + companion object : FreeMonadInstance +} + +typealias FreeHeroesDataSource = Free + +inline fun Free>.runList( + interpreter: FunctionK, MF: Monad = monad()): HK> = + this.foldMap(interpreter, MF) + +inline fun Free.runSingle( + interpreter: FunctionK, MF: Monad = monad()): HK = + this.foldMap(interpreter, MF) + +/** + * Module definition. Here we lift to the Free context all the operation blocks defined on the algebra. + */ +interface HeroesDataSource { + + fun getAll(): FreeHeroesDataSource> = + Free.liftF(HeroesDataSourceAlgebra.GetAll()) + + fun getSingle(heroId: String): FreeHeroesDataSource> = + Free.liftF(HeroesDataSourceAlgebra.GetSingle(heroId)) + + /** + * More complex operation using the resting operation blocks already lifted to Free. + */ + fun getAllFromAvengerComics(): FreeHeroesDataSource> = getAll().map { + it.filter { + it.comics.items.map { it.name }.filter { + it.contains("Avenger", true) + }.count() > 0 + } + } +} diff --git a/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/data/datasource/remote/DataMappers.kt b/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/data/datasource/remote/DataMappers.kt deleted file mode 100644 index 5dabeb4..0000000 --- a/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/data/datasource/remote/DataMappers.kt +++ /dev/null @@ -1,6 +0,0 @@ -package com.github.jorgecastillo.kotlinandroid.data.datasource.remote - -import com.github.jorgecastillo.kotlinandroid.domain.model.SuperHero -import com.karumi.marvelapiclient.model.CharacterDto - -fun mapApiCharacterToSuperHero(character: CharacterDto) = SuperHero(character.name) diff --git a/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/data/datasource/remote/MarvelNetworkDataSource.kt b/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/data/datasource/remote/MarvelNetworkDataSource.kt deleted file mode 100644 index 57de8d3..0000000 --- a/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/data/datasource/remote/MarvelNetworkDataSource.kt +++ /dev/null @@ -1,71 +0,0 @@ -package com.github.jorgecastillo.kotlinandroid.data.datasource.remote - -import com.github.jorgecastillo.kotlinandroid.di.context.SuperHeroesContext.GetHeroDetailsContext -import com.github.jorgecastillo.kotlinandroid.di.context.SuperHeroesContext.GetHeroesContext -import com.github.jorgecastillo.kotlinandroid.domain.model.CharacterError -import com.github.jorgecastillo.kotlinandroid.domain.model.CharacterError.AuthenticationError -import com.github.jorgecastillo.kotlinandroid.domain.model.CharacterError.NotFoundError -import com.github.jorgecastillo.kotlinandroid.domain.model.CharacterError.UnknownServerError -import com.github.jorgecastillo.kotlinandroid.functional.MonadControl -import com.github.jorgecastillo.kotlinandroid.functional.monadControl -import com.karumi.marvelapiclient.MarvelApiException -import com.karumi.marvelapiclient.MarvelAuthApiException -import com.karumi.marvelapiclient.model.CharacterDto -import com.karumi.marvelapiclient.model.CharactersQuery -import kategory.HK -import kategory.Option -import kategory.binding -import java.net.HttpURLConnection - -/* - * This is the network data source. Calls are made using Karumi's MarvelApiClient. - * @see "https://github.com/Karumi/MarvelApiClientAndroid" - * - * Both requests return a new Reader enclosing an action to resolve when you provide them with the - * required execution context. - * - * The getHeroesFromAvengerComicsUseCase() method maps the fetchAllHeroes() result to filter the list with just the - * elements with given conditions. It's returning heroes appearing on comics with the "Avenger" - * word in the title. Yep, I wanted to retrieve Avengers but the Marvel API is a bit weird - * sometimes. - */ -fun exceptionAsCharacterError(e: Throwable): CharacterError = - when (e) { - is MarvelAuthApiException -> AuthenticationError - is MarvelApiException -> - if (e.httpCode == HttpURLConnection.HTTP_NOT_FOUND) NotFoundError - else UnknownServerError(Option.Some(e)) - else -> UnknownServerError((Option.Some(e))) - } - - -inline fun fetchAllHeroes( - C: MonadControl = monadControl()): HK> = - C.binding { - val query = CharactersQuery.Builder.create().withOffset(0).withLimit(50).build() - val ctx = C.ask().bind() - C.catch( - { ctx.apiClient.getAll(query).response.characters }, - { exceptionAsCharacterError(it) } - ) - } - -inline fun fetchHeroDetails(heroId: String, - C: MonadControl = monadControl()): HK = - C.binding { - val ctx = C.ask().bind() - C.catch( - { ctx.apiClient.getCharacter(heroId).response }, - { exceptionAsCharacterError(it) } - ) - } - -inline fun fetchHeroesFromAvengerComics( - C: MonadControl = monadControl()): HK> = - C.map(fetchAllHeroes(C), { - it.filter { - it.comics.items.map { it.name }.filter { - it.contains("Avenger", true) - }.count() > 0 - } - }) diff --git a/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/data/interpreter/AsyncResultHeroesDataSourceInterpreter.kt b/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/data/interpreter/AsyncResultHeroesDataSourceInterpreter.kt new file mode 100644 index 0000000..3e528b3 --- /dev/null +++ b/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/data/interpreter/AsyncResultHeroesDataSourceInterpreter.kt @@ -0,0 +1,79 @@ +package com.github.jorgecastillo.kotlinandroid.data.interpreter + +import com.github.jorgecastillo.kotlinandroid.data.algebra.HeroesDataSource +import com.github.jorgecastillo.kotlinandroid.data.algebra.HeroesDataSourceAlgebra +import com.github.jorgecastillo.kotlinandroid.data.algebra.HeroesDataSourceAlgebraHK +import com.github.jorgecastillo.kotlinandroid.data.algebra.ev +import com.github.jorgecastillo.kotlinandroid.di.context.SuperHeroesContext +import com.github.jorgecastillo.kotlinandroid.di.context.SuperHeroesContext.GetHeroesContext +import com.github.jorgecastillo.kotlinandroid.domain.model.CharacterError +import com.github.jorgecastillo.kotlinandroid.domain.model.CharacterError.AuthenticationError +import com.github.jorgecastillo.kotlinandroid.domain.model.CharacterError.NotFoundError +import com.github.jorgecastillo.kotlinandroid.domain.model.CharacterError.UnknownServerError +import com.github.jorgecastillo.kotlinandroid.functional.AsyncResult +import com.github.jorgecastillo.kotlinandroid.functional.AsyncResultHK +import com.github.jorgecastillo.kotlinandroid.functional.AsyncResultKind +import com.github.jorgecastillo.kotlinandroid.functional.AsyncResultMonadReaderInstance +import com.github.jorgecastillo.kotlinandroid.functional.ev +import com.github.jorgecastillo.kotlinandroid.functional.monadReader +import com.karumi.marvelapiclient.MarvelApiException +import com.karumi.marvelapiclient.MarvelAuthApiException +import com.karumi.marvelapiclient.model.CharacterDto +import com.karumi.marvelapiclient.model.CharactersQuery.Builder +import kategory.FunctionK +import kategory.HK +import kategory.Option +import kategory.binding +import kategory.foldMap +import java.net.HttpURLConnection + +fun test(): Unit { + val heroesDS = object : HeroesDataSource { + } + + val MR = AsyncResult.monadReader() + heroesDS.getAll().foldMap(asyncResultDataSourceInterpreter(MR), MR) +} + +inline fun asyncResultDataSourceInterpreter( + ARM: AsyncResultMonadReaderInstance): FunctionK = + object : FunctionK { + override fun invoke(fa: HK): AsyncResult { + val op = fa.ev() + return when (op) { + is HeroesDataSourceAlgebra.GetAll -> getAllHeroesAsyncResult(ARM) as HK + is HeroesDataSourceAlgebra.GetSingle -> getHeroDetails(ARM, op.heroId) as HK + } + } + } + +fun getAllHeroesAsyncResult( + AR: AsyncResultMonadReaderInstance): AsyncResult> { + return AR.binding { + val query = Builder.create().withOffset(0).withLimit(50).build() + val ctx = AR.ask().bind() + AR.catch( + { ctx.apiClient.getAll(query).response.characters.toList() }, + { exceptionAsCharacterError(it) } + ) + }.ev() +} + +fun getHeroDetails(AR: AsyncResultMonadReaderInstance, + heroId: String): AsyncResult> = + AR.binding { + val ctx = AR.ask().bind() + AR.catch( + { listOf(ctx.apiClient.getCharacter(heroId).response) }, + { exceptionAsCharacterError(it) } + ).ev() + }.ev() + +fun exceptionAsCharacterError(e: Throwable): CharacterError = + when (e) { + is MarvelAuthApiException -> AuthenticationError + is MarvelApiException -> + if (e.httpCode == HttpURLConnection.HTTP_NOT_FOUND) NotFoundError + else UnknownServerError(Option.Some(e)) + else -> UnknownServerError((Option.Some(e))) + } diff --git a/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/functional/AsyncResult.kt b/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/functional/AsyncResult.kt new file mode 100644 index 0000000..58793a9 --- /dev/null +++ b/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/functional/AsyncResult.kt @@ -0,0 +1,173 @@ +package com.github.jorgecastillo.kotlinandroid.functional + +import com.github.jorgecastillo.kotlinandroid.di.context.SuperHeroesContext +import com.github.jorgecastillo.kotlinandroid.domain.model.CharacterError +import com.github.jorgecastillo.kotlinandroid.functional.AsyncResult.Companion.KMR +import kategory.Applicative +import kategory.Either +import kategory.EitherT +import kategory.EitherTKindPartial +import kategory.Functor +import kategory.HK +import kategory.Kleisli +import kategory.KleisliMonadErrorInstance +import kategory.KleisliMonadErrorInstanceImplicits +import kategory.KleisliMonadReaderInstance +import kategory.KleisliMonadReaderInstanceImplicits +import kategory.Monad +import kategory.MonadError +import kategory.MonadReader +import kategory.andThen +import kategory.ev + +typealias Result = Kleisli, D, A> + +fun Result.asyncResult(): AsyncResult = AsyncResult(this) + +class AsyncResultHK private constructor() + +typealias AsyncResultKind = kategory.HK2 + +typealias AsyncResultKindPartial = kategory.HK +@Suppress("UNCHECKED_CAST") +inline fun AsyncResultKind.ev(): AsyncResult = + this as AsyncResult + +class AsyncResult(val value: Result) : AsyncResultKind { + + fun map(f: (A) -> B): AsyncResult = + value.map(f, ETM()).asyncResult() + + fun flatMap(f: (A) -> AsyncResultKind): AsyncResult = + value.flatMap(f.andThen { it.ev().value }, ETM()).asyncResult() + + fun ap(ff: AsyncResultKind B>): AsyncResult = ff.ev().flatMap { this.ev().map(it) } + + fun handleErrorWith(f: (CharacterError) -> AsyncResult): AsyncResult = + AsyncResult(KME().handleErrorWith(value, f.andThen { it.ev().value })) + + fun run(d: D): EitherT = value.run(d).ev() + + companion object { + + fun ETM(): MonadError, CharacterError> = + EitherT.monadError() + + fun KME(): KleisliMonadErrorInstance, D, CharacterError> = + KleisliMonadErrorInstanceImplicits.instance(ETM()) + + fun KMR(): KleisliMonadReaderInstance, D> = + KleisliMonadReaderInstanceImplicits.instance(ETM()) + + fun tailRecM(a: A, f: (A) -> AsyncResultKind>): AsyncResult = + AsyncResult(KME().tailRecM(a, f.andThen { it.ev().value })) + + fun pure(a: A): AsyncResult = + AsyncResult(KME().pure(a)) + + fun ask(): AsyncResult = + AsyncResult(KMR().ask()) + + fun unit(): AsyncResult = pure(Unit) + + fun local( + f: (D) -> D, fa: AsyncResultKind): AsyncResult = + AsyncResult(KMR().local(f, fa.ev().value)) + } +} + +interface AsyncResultFunctorInstance : Functor> { + + override fun map(fa: AsyncResultKind, f: (A) -> B): AsyncResult = + fa.ev().map(f) + +} + +object AsyncResultFunctorInstanceImplicits { + @JvmStatic + fun instance(): AsyncResultFunctorInstance = + object : AsyncResultFunctorInstance {} +} + +fun AsyncResult.Companion.functor(): AsyncResultFunctorInstance = + AsyncResultFunctorInstanceImplicits.instance() + +interface AsyncResultApplicativeInstance : AsyncResultFunctorInstance, Applicative> { + + override fun map(fa: AsyncResultKind, f: (A) -> B): AsyncResult = + fa.ev().map(f) + + override fun ap(fa: AsyncResultKind, ff: HK, (A) -> B>): AsyncResult = + fa.ev().ap(ff) + + override fun pure(a: A): AsyncResult = + AsyncResult.pure(a) + +} + +object AsyncResultApplicativeInstanceImplicits { + @JvmStatic + fun instance(): AsyncResultApplicativeInstance = + object : AsyncResultApplicativeInstance {} +} + +fun AsyncResult.Companion.applicative(): AsyncResultApplicativeInstance = + AsyncResultApplicativeInstanceImplicits.instance() + +interface AsyncResultMonadInstance : AsyncResultApplicativeInstance, Monad> { + + override fun flatMap(fa: AsyncResultKind, f: (A) -> AsyncResultKind): AsyncResultKind = + fa.ev().flatMap(f) + + override fun ap(fa: AsyncResultKind, ff: HK, (A) -> B>): AsyncResult = + fa.ev().ap(ff) + + override fun tailRecM(a: A, f: (A) -> AsyncResultKind>): AsyncResultKind = + AsyncResult.tailRecM(a, f) +} + +object AsyncResultMonadInstanceImplicits { + @JvmStatic + fun instance(): AsyncResultMonadInstance = + object : AsyncResultMonadInstance {} +} + +fun AsyncResult.Companion.monad(): AsyncResultMonadInstance = + AsyncResultMonadInstanceImplicits.instance() + +interface AsyncResultMonadErrorInstance : AsyncResultMonadInstance, MonadError, CharacterError> { + + override fun raiseError(e: CharacterError): AsyncResult = + AsyncResult(AsyncResult.KME().raiseError(e)) + + override fun handleErrorWith(fa: AsyncResultKind, + f: (CharacterError) -> AsyncResultKind): AsyncResult = + fa.ev().handleErrorWith(f.andThen { it.ev() }) + +} + +object AsyncResultMonadErrorInstanceImplicits { + @JvmStatic + fun instance(): AsyncResultMonadErrorInstance = + object : AsyncResultMonadErrorInstance {} +} + +fun AsyncResult.Companion.monadError(): AsyncResultMonadErrorInstance = + AsyncResultMonadErrorInstanceImplicits.instance() + +interface AsyncResultMonadReaderInstance : AsyncResultMonadErrorInstance, MonadReader, D> { + + override fun ask(): HK, D> = AsyncResult(KMR().ask()) + + override fun local(f: (D) -> D, fa: HK, A>): HK, A> = + AsyncResult(KMR().local(f, fa.ev().value)) +} + +object AsyncResultMonadReaderInstanceImplicits { + @JvmStatic + fun instance(): AsyncResultMonadReaderInstance = + object : AsyncResultMonadReaderInstance {} +} + +fun AsyncResult.Companion.monadReader(): AsyncResultMonadReaderInstance = + AsyncResultMonadReaderInstanceImplicits.instance() diff --git a/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/functional/AsyncResultMonadControl.kt b/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/functional/AsyncResultMonadControl.kt deleted file mode 100644 index eecdefb..0000000 --- a/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/functional/AsyncResultMonadControl.kt +++ /dev/null @@ -1,111 +0,0 @@ -package com.github.jorgecastillo.kotlinandroid.functional - -import com.github.jorgecastillo.kotlinandroid.di.context.SuperHeroesContext -import com.github.jorgecastillo.kotlinandroid.di.context.SuperHeroesContext.GetHeroDetailsContext -import com.github.jorgecastillo.kotlinandroid.di.context.SuperHeroesContext.GetHeroesContext -import com.github.jorgecastillo.kotlinandroid.domain.model.CharacterError -import kategory.Either -import kategory.EitherT -import kategory.EitherTKindPartial -import kategory.HK -import kategory.Kleisli -import kategory.KleisliMonadErrorInstance -import kategory.KleisliMonadErrorInstanceImplicits -import kategory.KleisliMonadReaderInstance -import kategory.KleisliMonadReaderInstanceImplicits -import kategory.MonadError -import kategory.MonadReader -import kategory.Tuple2 -import kategory.Typeclass -import kategory.andThen -import kategory.instance - -class AsyncResultHK private constructor() -typealias AsyncResultKind = kategory.HK2 -typealias AsyncResultKindPartial = kategory.HK - -@Suppress("UNCHECKED_CAST") -inline fun AsyncResultKind.ev(): AsyncResult = - this as AsyncResult - -typealias Result = Kleisli, D, A> - -class AsyncResult( - val value: Result) : AsyncResultKind { - - fun run(ctx: D): HK, A> = value.run(ctx) - - companion object { - inline operator fun invoke(): MonadControl, D, CharacterError> = monadControl() - } -} - -inline fun monadControl(): MonadControl = - if (D::class == GetHeroesContext::class) { - AsyncResultMonadControl.Companion.getHeroesControl() as MonadControl - } else { - AsyncResultMonadControl.Companion.getHeroDetailsControl() as MonadControl - } - -interface MonadControl : - MonadError, - MonadReader, - Typeclass - -interface AsyncResultMonadControl : MonadControl, D, CharacterError> { - - companion object { - } - - fun ETME(): MonadError, CharacterError> = - EitherT.monadError() - - fun KME(): KleisliMonadErrorInstance, D, CharacterError> = - KleisliMonadErrorInstanceImplicits.instance(ETME()) - - fun KMR(): KleisliMonadReaderInstance, D> = - KleisliMonadReaderInstanceImplicits.instance(ETME()) - - override fun map(fa: HK, A>, f: (A) -> B): AsyncResult { - return AsyncResult(KME().map(fa.ev().value, f)) - } - - override fun product(fa: HK, A>, - fb: HK, B>): AsyncResult> { - return AsyncResult(KME().product(fa.ev().value, fb.ev().value)) - } - - override fun flatMap(fa: HK, A>, - f: (A) -> HK, B>): AsyncResult { - return AsyncResult(KME().flatMap(fa.ev().value, f.andThen { it.ev().value })) - } - - override fun handleErrorWith(fa: HK, A>, - f: (CharacterError) -> HK, A>): AsyncResult { - return AsyncResult(KME().handleErrorWith(fa.ev().value, f.andThen { it.ev().value })) - } - - override fun tailRecM(a: A, - f: (A) -> HK, Either>): AsyncResult { - return AsyncResult(KME().tailRecM(a, f.andThen { it.ev().value })) - } - - override fun raiseError(e: CharacterError): AsyncResult = - AsyncResult(KME().raiseError(e)) - - override fun pure(a: A): AsyncResult = - AsyncResult(KME().pure(a)) - - override fun ask(): AsyncResult = - AsyncResult(KMR().ask()) - - override fun local(f: (D) -> D, fa: HK, A>): AsyncResult { - return AsyncResult(KMR().local(f, fa.ev().value)) - } -} - -@instance(AsyncResultMonadControl::class) -interface GetHeroesControlAsyncResultMonadControlInstance : AsyncResultMonadControl - -@instance(AsyncResultMonadControl::class) -interface GetHeroDetailsControlAsyncResultMonadControlInstance : AsyncResultMonadControl diff --git a/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/functional/Future.kt b/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/functional/Future.kt index ac53056..6eeda50 100644 --- a/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/functional/Future.kt +++ b/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/functional/Future.kt @@ -14,40 +14,40 @@ import kotlinx.coroutines.experimental.launch @deriving(Functor::class, Applicative::class, Monad::class) class Future : FutureKind { - private val deferred: Deferred + private val deferred: Deferred - private constructor(deferred: Deferred) { - this.deferred = deferred - } + private constructor(deferred: Deferred) { + this.deferred = deferred + } - constructor(f: () -> T) : this(async(CommonPool) { f() }) + constructor(f: () -> T) : this(async(CommonPool) { f() }) - fun map(f: (T) -> X): Future = Future(async(CommonPool) { f(deferred.await()) }) + fun map(f: (T) -> X): Future = Future(async(CommonPool) { f(deferred.await()) }) - fun flatMap(f: (T) -> FutureKind): Future = - Future(async(CommonPool) { f(deferred.await()).ev().deferred.await() }) + fun flatMap(f: (T) -> FutureKind): Future = + Future(async(CommonPool) { f(deferred.await()).ev().deferred.await() }) - fun ap(ff: FutureKind<(T) -> B>): Future = - zip(ff).map { it.b(it.a) } + fun ap(ff: FutureKind<(T) -> B>): Future = + zip(ff).map { it.b(it.a) } - fun zip(fb: FutureKind): Future> = - flatMap { a -> fb.ev().map { b -> Tuple2(a, b) } } + fun zip(fb: FutureKind): Future> = + flatMap { a -> fb.ev().map { b -> Tuple2(a, b) } } - fun onComplete(f: (T) -> Unit) { - launch(UI) { - f(deferred.await()) + fun onComplete(f: (T) -> Unit) { + launch(UI) { + f(deferred.await()) + } } - } - companion object { + companion object { - fun tailRecM(a: A, f: (A) -> FutureKind>): Future = - f(a).ev().flatMap { - it.fold({ tailRecM(a, f).ev() }, { Future.pure(it) }) - } + fun tailRecM(a: A, f: (A) -> FutureKind>): Future = + f(a).ev().flatMap { + it.fold({ tailRecM(a, f).ev() }, { Future.pure(it) }) + } - fun pure(a: A): Future = - Future({ a }) + fun pure(a: A): Future = + Future({ a }) - } + } } From bfcff9945594dfa281bd619b18fd2cd2a937b28d Mon Sep 17 00:00:00 2001 From: Jorge Castillo Date: Fri, 6 Oct 2017 19:06:30 +0200 Subject: [PATCH 04/19] fix problems --- .../AsyncResultHeroesDataSourceInterpreter.kt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/data/interpreter/AsyncResultHeroesDataSourceInterpreter.kt b/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/data/interpreter/AsyncResultHeroesDataSourceInterpreter.kt index 3e528b3..12b068d 100644 --- a/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/data/interpreter/AsyncResultHeroesDataSourceInterpreter.kt +++ b/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/data/interpreter/AsyncResultHeroesDataSourceInterpreter.kt @@ -35,14 +35,14 @@ fun test(): Unit { heroesDS.getAll().foldMap(asyncResultDataSourceInterpreter(MR), MR) } -inline fun asyncResultDataSourceInterpreter( - ARM: AsyncResultMonadReaderInstance): FunctionK = - object : FunctionK { - override fun invoke(fa: HK): AsyncResult { +inline fun asyncResultDataSourceInterpreter( + ARM: AsyncResultMonadReaderInstance): FunctionK = + object : FunctionK { + override fun invoke(fa: HK): HK { val op = fa.ev() return when (op) { - is HeroesDataSourceAlgebra.GetAll -> getAllHeroesAsyncResult(ARM) as HK - is HeroesDataSourceAlgebra.GetSingle -> getHeroDetails(ARM, op.heroId) as HK + is HeroesDataSourceAlgebra.GetAll -> getAllHeroesAsyncResult(ARM) as HK + is HeroesDataSourceAlgebra.GetSingle -> getHeroDetails(ARM, op.heroId) as HK } } } From d398850e817ecc26ebfcb469581128ee75a0ec7e Mon Sep 17 00:00:00 2001 From: Jorge Castillo Date: Fri, 6 Oct 2017 19:07:39 +0200 Subject: [PATCH 05/19] optimize imports --- .../data/interpreter/AsyncResultHeroesDataSourceInterpreter.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/data/interpreter/AsyncResultHeroesDataSourceInterpreter.kt b/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/data/interpreter/AsyncResultHeroesDataSourceInterpreter.kt index 12b068d..1ad84c5 100644 --- a/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/data/interpreter/AsyncResultHeroesDataSourceInterpreter.kt +++ b/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/data/interpreter/AsyncResultHeroesDataSourceInterpreter.kt @@ -11,8 +11,6 @@ import com.github.jorgecastillo.kotlinandroid.domain.model.CharacterError.Authen import com.github.jorgecastillo.kotlinandroid.domain.model.CharacterError.NotFoundError import com.github.jorgecastillo.kotlinandroid.domain.model.CharacterError.UnknownServerError import com.github.jorgecastillo.kotlinandroid.functional.AsyncResult -import com.github.jorgecastillo.kotlinandroid.functional.AsyncResultHK -import com.github.jorgecastillo.kotlinandroid.functional.AsyncResultKind import com.github.jorgecastillo.kotlinandroid.functional.AsyncResultMonadReaderInstance import com.github.jorgecastillo.kotlinandroid.functional.ev import com.github.jorgecastillo.kotlinandroid.functional.monadReader From 9b22ce5470fd5f2764731067e68858baf68e0cdf Mon Sep 17 00:00:00 2001 From: Jorge Castillo Date: Fri, 6 Oct 2017 19:13:57 +0200 Subject: [PATCH 06/19] rename algebra to be shared --- .../kotlinandroid/data/HeroesRepository.kt | 16 +++--- .../kotlinandroid/data/algebra/algebras.kt | 49 ++++++++----------- .../AsyncResultHeroesDataSourceInterpreter.kt | 7 ++- 3 files changed, 32 insertions(+), 40 deletions(-) diff --git a/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/data/HeroesRepository.kt b/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/data/HeroesRepository.kt index 064d5f0..c2fde7b 100644 --- a/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/data/HeroesRepository.kt +++ b/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/data/HeroesRepository.kt @@ -4,9 +4,9 @@ import com.github.jorgecastillo.kotlinandroid.data.CachePolicy.LocalFirst import com.github.jorgecastillo.kotlinandroid.data.CachePolicy.LocalOnly import com.github.jorgecastillo.kotlinandroid.data.CachePolicy.NetworkFirst import com.github.jorgecastillo.kotlinandroid.data.CachePolicy.NetworkOnly -import com.github.jorgecastillo.kotlinandroid.data.interpreter.fetchAllHeroes +import com.github.jorgecastillo.kotlinandroid.data.algebra.FreeHeroesAlgebra +import com.github.jorgecastillo.kotlinandroid.data.algebra.getAllHeroes import com.github.jorgecastillo.kotlinandroid.data.interpreter.getHeroDetails -import com.github.jorgecastillo.kotlinandroid.data.interpreter.fetchHeroesFromAvengerComics import com.karumi.marvelapiclient.model.CharacterDto import kategory.HK @@ -17,14 +17,14 @@ sealed class CachePolicy { object LocalFirst : CachePolicy() } -inline fun getHeroesWithCachePolicy(policy: CachePolicy): HK> = when (policy) { - is NetworkOnly -> fetchAllHeroes() - is NetworkFirst -> fetchAllHeroes() // TODO change to conditional call - is LocalOnly -> fetchAllHeroes() // TODO change to local only cache call - is LocalFirst -> fetchAllHeroes() // TODO change to conditional call +inline fun getHeroesWithCachePolicy(policy: CachePolicy): FreeHeroesAlgebra> = when (policy) { + is NetworkOnly -> getAllHeroes() + is NetworkFirst -> getAllHeroes() // TODO change to conditional call + is LocalOnly -> getAllHeroes() // TODO change to local only cache call + is LocalFirst -> getAllHeroes() // TODO change to conditional call } -inline fun getHeroDetails(policy: CachePolicy, heroId: String): HK = when (policy) { +inline fun getHeroDetails(policy: CachePolicy, heroId: String): FreeHeroesAlgebra> = when (policy) { is NetworkOnly -> getHeroDetails(heroId) is NetworkFirst -> getHeroDetails( heroId) // TODO change to conditional call diff --git a/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/data/algebra/algebras.kt b/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/data/algebra/algebras.kt index 4ee25d0..d7b3542 100644 --- a/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/data/algebra/algebras.kt +++ b/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/data/algebra/algebras.kt @@ -14,42 +14,35 @@ import kategory.monad /** * Algebra for Hero data sources. Algebras are defined by a sealed class (ADT) with a limited amount of implementations reflecting the operations available. */ -@higherkind sealed class HeroesDataSourceAlgebra : HeroesDataSourceAlgebraKind { +@higherkind sealed class HeroesAlgebra : HeroesAlgebraKind { - class GetAll : HeroesDataSourceAlgebra>() - class GetSingle(val heroId: String) : HeroesDataSourceAlgebra>() - companion object : FreeMonadInstance + class GetAll : HeroesAlgebra>() + class GetSingle(val heroId: String) : HeroesAlgebra>() + companion object : FreeMonadInstance } -typealias FreeHeroesDataSource = Free +typealias FreeHeroesAlgebra = Free -inline fun Free>.runList( - interpreter: FunctionK, MF: Monad = monad()): HK> = +inline fun Free>.run( + interpreter: FunctionK, MF: Monad = monad()): HK> = this.foldMap(interpreter, MF) -inline fun Free.runSingle( - interpreter: FunctionK, MF: Monad = monad()): HK = - this.foldMap(interpreter, MF) +/** + * Module definition (Data Source methods). Here we lift to the Free context all the operation blocks defined on the algebra. + */ +fun getAllHeroes(): FreeHeroesAlgebra> = + Free.liftF(HeroesAlgebra.GetAll()) + +fun getSingleHero(heroId: String): FreeHeroesAlgebra> = + Free.liftF(HeroesAlgebra.GetSingle(heroId)) /** - * Module definition. Here we lift to the Free context all the operation blocks defined on the algebra. + * More complex operation using the resting operation blocks already lifted to Free. */ -interface HeroesDataSource { - - fun getAll(): FreeHeroesDataSource> = - Free.liftF(HeroesDataSourceAlgebra.GetAll()) - - fun getSingle(heroId: String): FreeHeroesDataSource> = - Free.liftF(HeroesDataSourceAlgebra.GetSingle(heroId)) - - /** - * More complex operation using the resting operation blocks already lifted to Free. - */ - fun getAllFromAvengerComics(): FreeHeroesDataSource> = getAll().map { - it.filter { - it.comics.items.map { it.name }.filter { - it.contains("Avenger", true) - }.count() > 0 - } +fun getAllFromAvengerComics(): FreeHeroesAlgebra> = getAllHeroes().map { + it.filter { + it.comics.items.map { it.name }.filter { + it.contains("Avenger", true) + }.count() > 0 } } diff --git a/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/data/interpreter/AsyncResultHeroesDataSourceInterpreter.kt b/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/data/interpreter/AsyncResultHeroesDataSourceInterpreter.kt index 1ad84c5..a6220db 100644 --- a/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/data/interpreter/AsyncResultHeroesDataSourceInterpreter.kt +++ b/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/data/interpreter/AsyncResultHeroesDataSourceInterpreter.kt @@ -1,7 +1,7 @@ package com.github.jorgecastillo.kotlinandroid.data.interpreter import com.github.jorgecastillo.kotlinandroid.data.algebra.HeroesDataSource -import com.github.jorgecastillo.kotlinandroid.data.algebra.HeroesDataSourceAlgebra +import com.github.jorgecastillo.kotlinandroid.data.algebra.HeroesAlgebra import com.github.jorgecastillo.kotlinandroid.data.algebra.HeroesDataSourceAlgebraHK import com.github.jorgecastillo.kotlinandroid.data.algebra.ev import com.github.jorgecastillo.kotlinandroid.di.context.SuperHeroesContext @@ -22,7 +22,6 @@ import kategory.FunctionK import kategory.HK import kategory.Option import kategory.binding -import kategory.foldMap import java.net.HttpURLConnection fun test(): Unit { @@ -39,8 +38,8 @@ inline fun asyncResultDataSourceInterpreter( override fun invoke(fa: HK): HK { val op = fa.ev() return when (op) { - is HeroesDataSourceAlgebra.GetAll -> getAllHeroesAsyncResult(ARM) as HK - is HeroesDataSourceAlgebra.GetSingle -> getHeroDetails(ARM, op.heroId) as HK + is HeroesAlgebra.GetAll -> getAllHeroesAsyncResult(ARM) as HK + is HeroesAlgebra.GetSingle -> getHeroDetails(ARM, op.heroId) as HK } } } From db9d517ad572c6812f33f7dabf4f364cff5a0576 Mon Sep 17 00:00:00 2001 From: Jorge Castillo Date: Fri, 6 Oct 2017 19:15:32 +0200 Subject: [PATCH 07/19] fix repository layer --- .../kotlinandroid/data/HeroesRepository.kt | 43 +++++++++---------- 1 file changed, 21 insertions(+), 22 deletions(-) diff --git a/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/data/HeroesRepository.kt b/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/data/HeroesRepository.kt index c2fde7b..bb6bd82 100644 --- a/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/data/HeroesRepository.kt +++ b/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/data/HeroesRepository.kt @@ -5,10 +5,10 @@ import com.github.jorgecastillo.kotlinandroid.data.CachePolicy.LocalOnly import com.github.jorgecastillo.kotlinandroid.data.CachePolicy.NetworkFirst import com.github.jorgecastillo.kotlinandroid.data.CachePolicy.NetworkOnly import com.github.jorgecastillo.kotlinandroid.data.algebra.FreeHeroesAlgebra +import com.github.jorgecastillo.kotlinandroid.data.algebra.getAllFromAvengerComics import com.github.jorgecastillo.kotlinandroid.data.algebra.getAllHeroes -import com.github.jorgecastillo.kotlinandroid.data.interpreter.getHeroDetails +import com.github.jorgecastillo.kotlinandroid.data.algebra.getSingleHero import com.karumi.marvelapiclient.model.CharacterDto -import kategory.HK sealed class CachePolicy { object NetworkOnly : CachePolicy() @@ -17,27 +17,26 @@ sealed class CachePolicy { object LocalFirst : CachePolicy() } -inline fun getHeroesWithCachePolicy(policy: CachePolicy): FreeHeroesAlgebra> = when (policy) { - is NetworkOnly -> getAllHeroes() - is NetworkFirst -> getAllHeroes() // TODO change to conditional call - is LocalOnly -> getAllHeroes() // TODO change to local only cache call - is LocalFirst -> getAllHeroes() // TODO change to conditional call -} +fun getHeroesWithCachePolicy(policy: CachePolicy): FreeHeroesAlgebra> = + when (policy) { + is NetworkOnly -> getAllHeroes() + is NetworkFirst -> getAllHeroes() // TODO change to conditional call + is LocalOnly -> getAllHeroes() // TODO change to local only cache call + is LocalFirst -> getAllHeroes() // TODO change to conditional call + } -inline fun getHeroDetails(policy: CachePolicy, heroId: String): FreeHeroesAlgebra> = when (policy) { - is NetworkOnly -> getHeroDetails(heroId) - is NetworkFirst -> getHeroDetails( - heroId) // TODO change to conditional call - is LocalOnly -> getHeroDetails( - heroId) // TODO change to local only cache call - is LocalFirst -> getHeroDetails( - heroId) // TODO change to conditional call -} +fun getHeroDetails(policy: CachePolicy, heroId: String): FreeHeroesAlgebra> = + when (policy) { + is NetworkOnly -> getSingleHero(heroId) + is NetworkFirst -> getSingleHero(heroId) // TODO change to conditional call + is LocalOnly -> getSingleHero(heroId) // TODO change to local only cache call + is LocalFirst -> getSingleHero(heroId) // TODO change to conditional call + } -inline fun getHeroesFromAvengerComicsWithCachePolicy(policy: CachePolicy): HK> = +fun getHeroesFromAvengerComicsWithCachePolicy(policy: CachePolicy): FreeHeroesAlgebra> = when (policy) { - is NetworkOnly -> fetchHeroesFromAvengerComics() - is NetworkFirst -> fetchHeroesFromAvengerComics() // TODO change to conditional call - is LocalOnly -> fetchHeroesFromAvengerComics() // TODO change to local only cache call - is LocalFirst -> fetchHeroesFromAvengerComics() // TODO change to conditional call + is NetworkOnly -> getAllFromAvengerComics() + is NetworkFirst -> getAllFromAvengerComics() // TODO change to conditional call + is LocalOnly -> getAllFromAvengerComics() // TODO change to local only cache call + is LocalFirst -> getAllFromAvengerComics() // TODO change to conditional call } From 2fed37bb71b35136bc3ba1b6214e35dec6a04690 Mon Sep 17 00:00:00 2001 From: Jorge Castillo Date: Fri, 6 Oct 2017 19:16:20 +0200 Subject: [PATCH 08/19] fix use cases --- .../kotlinandroid/domain/usecase/HeroesUseCases.kt | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/domain/usecase/HeroesUseCases.kt b/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/domain/usecase/HeroesUseCases.kt index ded951c..7456bff 100644 --- a/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/domain/usecase/HeroesUseCases.kt +++ b/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/domain/usecase/HeroesUseCases.kt @@ -1,22 +1,17 @@ package com.github.jorgecastillo.kotlinandroid.domain.usecase import com.github.jorgecastillo.kotlinandroid.data.CachePolicy.NetworkOnly +import com.github.jorgecastillo.kotlinandroid.data.algebra.FreeHeroesAlgebra import com.github.jorgecastillo.kotlinandroid.data.getHeroDetails import com.github.jorgecastillo.kotlinandroid.data.getHeroesFromAvengerComicsWithCachePolicy import com.github.jorgecastillo.kotlinandroid.data.getHeroesWithCachePolicy -import com.github.jorgecastillo.kotlinandroid.di.context.SuperHeroesContext.GetHeroDetailsContext -import com.github.jorgecastillo.kotlinandroid.di.context.SuperHeroesContext.GetHeroesContext -import com.github.jorgecastillo.kotlinandroid.domain.model.CharacterError -import com.github.jorgecastillo.kotlinandroid.functional.MonadControl -import com.github.jorgecastillo.kotlinandroid.functional.monadControl import com.karumi.marvelapiclient.model.CharacterDto -import kategory.HK -inline fun getHeroesUseCase(): HK> = +fun getHeroesUseCase(): FreeHeroesAlgebra> = getHeroesWithCachePolicy(NetworkOnly) -inline fun getHeroDetailsUseCase(heroId: String): HK = +fun getHeroDetailsUseCase(heroId: String): FreeHeroesAlgebra> = getHeroDetails(NetworkOnly, heroId) -inline fun getHeroesFromAvengerComicsUseCase(): HK> = +fun getHeroesFromAvengerComicsUseCase(): FreeHeroesAlgebra> = getHeroesFromAvengerComicsWithCachePolicy(NetworkOnly) From 9804325edee546703565aa469f7aa49d4ec8d332 Mon Sep 17 00:00:00 2001 From: Jorge Castillo Date: Fri, 6 Oct 2017 19:21:27 +0200 Subject: [PATCH 09/19] rename interpreter --- ...reter.kt => AsyncResultHeroesInterpreter.kt} | 7 ++----- .../presentation/SuperHeroesPresentation.kt | 17 ++++++++++++++--- 2 files changed, 16 insertions(+), 8 deletions(-) rename free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/data/interpreter/{AsyncResultHeroesDataSourceInterpreter.kt => AsyncResultHeroesInterpreter.kt} (92%) diff --git a/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/data/interpreter/AsyncResultHeroesDataSourceInterpreter.kt b/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/data/interpreter/AsyncResultHeroesInterpreter.kt similarity index 92% rename from free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/data/interpreter/AsyncResultHeroesDataSourceInterpreter.kt rename to free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/data/interpreter/AsyncResultHeroesInterpreter.kt index a6220db..a62e543 100644 --- a/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/data/interpreter/AsyncResultHeroesDataSourceInterpreter.kt +++ b/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/data/interpreter/AsyncResultHeroesInterpreter.kt @@ -1,11 +1,9 @@ package com.github.jorgecastillo.kotlinandroid.data.interpreter -import com.github.jorgecastillo.kotlinandroid.data.algebra.HeroesDataSource import com.github.jorgecastillo.kotlinandroid.data.algebra.HeroesAlgebra import com.github.jorgecastillo.kotlinandroid.data.algebra.HeroesDataSourceAlgebraHK import com.github.jorgecastillo.kotlinandroid.data.algebra.ev import com.github.jorgecastillo.kotlinandroid.di.context.SuperHeroesContext -import com.github.jorgecastillo.kotlinandroid.di.context.SuperHeroesContext.GetHeroesContext import com.github.jorgecastillo.kotlinandroid.domain.model.CharacterError import com.github.jorgecastillo.kotlinandroid.domain.model.CharacterError.AuthenticationError import com.github.jorgecastillo.kotlinandroid.domain.model.CharacterError.NotFoundError @@ -13,7 +11,6 @@ import com.github.jorgecastillo.kotlinandroid.domain.model.CharacterError.Unknow import com.github.jorgecastillo.kotlinandroid.functional.AsyncResult import com.github.jorgecastillo.kotlinandroid.functional.AsyncResultMonadReaderInstance import com.github.jorgecastillo.kotlinandroid.functional.ev -import com.github.jorgecastillo.kotlinandroid.functional.monadReader import com.karumi.marvelapiclient.MarvelApiException import com.karumi.marvelapiclient.MarvelAuthApiException import com.karumi.marvelapiclient.model.CharacterDto @@ -24,13 +21,13 @@ import kategory.Option import kategory.binding import java.net.HttpURLConnection -fun test(): Unit { +/*fun test(): Unit { val heroesDS = object : HeroesDataSource { } val MR = AsyncResult.monadReader() heroesDS.getAll().foldMap(asyncResultDataSourceInterpreter(MR), MR) -} +}*/ inline fun asyncResultDataSourceInterpreter( ARM: AsyncResultMonadReaderInstance): FunctionK = diff --git a/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/presentation/SuperHeroesPresentation.kt b/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/presentation/SuperHeroesPresentation.kt index 5366e91..2b97561 100644 --- a/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/presentation/SuperHeroesPresentation.kt +++ b/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/presentation/SuperHeroesPresentation.kt @@ -1,5 +1,6 @@ package com.github.jorgecastillo.kotlinandroid.presentation +import com.github.jorgecastillo.kotlinandroid.data.algebra.HeroesAlgebraHK import com.github.jorgecastillo.kotlinandroid.di.context.SuperHeroesContext import com.github.jorgecastillo.kotlinandroid.di.context.SuperHeroesContext.GetHeroDetailsContext import com.github.jorgecastillo.kotlinandroid.di.context.SuperHeroesContext.GetHeroesContext @@ -14,6 +15,7 @@ import com.github.jorgecastillo.kotlinandroid.functional.monadControl import com.github.jorgecastillo.kotlinandroid.view.viewmodel.SuperHeroViewModel import com.karumi.marvelapiclient.model.CharacterDto import com.karumi.marvelapiclient.model.MarvelImage +import kategory.Free import kategory.HK import kategory.binding import kategory.flatMap @@ -45,8 +47,17 @@ fun displayErrors(ctx: SuperHeroesContext, c: CharacterError): Unit { } } -inline fun getSuperHeroes(C: MonadControl = monadControl()): HK = - C.binding { +inline fun getSuperHeroes(C: MonadControl = monadControl()): Free = + getHeroesUseCase().flatMap { + ctx.view.drawHeroes(result.map { + SuperHeroViewModel( + it.id, + it.name, + it.thumbnail.getImageUrl(MarvelImage.Size.PORTRAIT_UNCANNY), + it.description) + }) + } + /*C.binding { val ctx = C.ask().bind() val result = C.handleError(getHeroesUseCase(), { displayErrors(ctx, it); emptyList() }).bind() ctx.view.drawHeroes(result.map { @@ -57,7 +68,7 @@ inline fun getSuperHeroes(C: MonadControl getSuperHeroDetails(heroId: String, C: MonadControl = monadControl()): HK = From e7954f9f856a12269cf1b70dd5fcfa704dbe9477 Mon Sep 17 00:00:00 2001 From: Jorge Castillo Date: Fri, 6 Oct 2017 19:23:49 +0200 Subject: [PATCH 10/19] move free related classes to more vertical package --- .../jorgecastillo/kotlinandroid/data/HeroesRepository.kt | 8 ++++---- .../kotlinandroid/domain/usecase/HeroesUseCases.kt | 2 +- .../kotlinandroid/{data => free}/algebra/algebras.kt | 2 +- .../interpreter/AsyncResultHeroesInterpreter.kt | 8 ++++---- .../kotlinandroid/presentation/SuperHeroesPresentation.kt | 3 +-- 5 files changed, 11 insertions(+), 12 deletions(-) rename free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/{data => free}/algebra/algebras.kt (96%) rename free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/{data => free}/interpreter/AsyncResultHeroesInterpreter.kt (91%) diff --git a/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/data/HeroesRepository.kt b/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/data/HeroesRepository.kt index bb6bd82..43e9b10 100644 --- a/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/data/HeroesRepository.kt +++ b/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/data/HeroesRepository.kt @@ -4,10 +4,10 @@ import com.github.jorgecastillo.kotlinandroid.data.CachePolicy.LocalFirst import com.github.jorgecastillo.kotlinandroid.data.CachePolicy.LocalOnly import com.github.jorgecastillo.kotlinandroid.data.CachePolicy.NetworkFirst import com.github.jorgecastillo.kotlinandroid.data.CachePolicy.NetworkOnly -import com.github.jorgecastillo.kotlinandroid.data.algebra.FreeHeroesAlgebra -import com.github.jorgecastillo.kotlinandroid.data.algebra.getAllFromAvengerComics -import com.github.jorgecastillo.kotlinandroid.data.algebra.getAllHeroes -import com.github.jorgecastillo.kotlinandroid.data.algebra.getSingleHero +import com.github.jorgecastillo.kotlinandroid.free.algebra.FreeHeroesAlgebra +import com.github.jorgecastillo.kotlinandroid.free.algebra.getAllFromAvengerComics +import com.github.jorgecastillo.kotlinandroid.free.algebra.getAllHeroes +import com.github.jorgecastillo.kotlinandroid.free.algebra.getSingleHero import com.karumi.marvelapiclient.model.CharacterDto sealed class CachePolicy { diff --git a/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/domain/usecase/HeroesUseCases.kt b/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/domain/usecase/HeroesUseCases.kt index 7456bff..f40a0a2 100644 --- a/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/domain/usecase/HeroesUseCases.kt +++ b/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/domain/usecase/HeroesUseCases.kt @@ -1,7 +1,7 @@ package com.github.jorgecastillo.kotlinandroid.domain.usecase import com.github.jorgecastillo.kotlinandroid.data.CachePolicy.NetworkOnly -import com.github.jorgecastillo.kotlinandroid.data.algebra.FreeHeroesAlgebra +import com.github.jorgecastillo.kotlinandroid.free.algebra.FreeHeroesAlgebra import com.github.jorgecastillo.kotlinandroid.data.getHeroDetails import com.github.jorgecastillo.kotlinandroid.data.getHeroesFromAvengerComicsWithCachePolicy import com.github.jorgecastillo.kotlinandroid.data.getHeroesWithCachePolicy diff --git a/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/data/algebra/algebras.kt b/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/free/algebra/algebras.kt similarity index 96% rename from free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/data/algebra/algebras.kt rename to free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/free/algebra/algebras.kt index d7b3542..f793c49 100644 --- a/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/data/algebra/algebras.kt +++ b/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/free/algebra/algebras.kt @@ -1,4 +1,4 @@ -package com.github.jorgecastillo.kotlinandroid.data.algebra +package com.github.jorgecastillo.kotlinandroid.free.algebra import com.karumi.marvelapiclient.model.CharacterDto import kategory.Free diff --git a/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/data/interpreter/AsyncResultHeroesInterpreter.kt b/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/free/interpreter/AsyncResultHeroesInterpreter.kt similarity index 91% rename from free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/data/interpreter/AsyncResultHeroesInterpreter.kt rename to free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/free/interpreter/AsyncResultHeroesInterpreter.kt index a62e543..7d3a950 100644 --- a/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/data/interpreter/AsyncResultHeroesInterpreter.kt +++ b/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/free/interpreter/AsyncResultHeroesInterpreter.kt @@ -1,8 +1,8 @@ -package com.github.jorgecastillo.kotlinandroid.data.interpreter +package com.github.jorgecastillo.kotlinandroid.free.interpreter -import com.github.jorgecastillo.kotlinandroid.data.algebra.HeroesAlgebra -import com.github.jorgecastillo.kotlinandroid.data.algebra.HeroesDataSourceAlgebraHK -import com.github.jorgecastillo.kotlinandroid.data.algebra.ev +import com.github.jorgecastillo.kotlinandroid.free.algebra.HeroesAlgebra +import com.github.jorgecastillo.kotlinandroid.free.algebra.HeroesDataSourceAlgebraHK +import com.github.jorgecastillo.kotlinandroid.free.algebra.ev import com.github.jorgecastillo.kotlinandroid.di.context.SuperHeroesContext import com.github.jorgecastillo.kotlinandroid.domain.model.CharacterError import com.github.jorgecastillo.kotlinandroid.domain.model.CharacterError.AuthenticationError diff --git a/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/presentation/SuperHeroesPresentation.kt b/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/presentation/SuperHeroesPresentation.kt index 2b97561..dd557f2 100644 --- a/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/presentation/SuperHeroesPresentation.kt +++ b/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/presentation/SuperHeroesPresentation.kt @@ -1,6 +1,6 @@ package com.github.jorgecastillo.kotlinandroid.presentation -import com.github.jorgecastillo.kotlinandroid.data.algebra.HeroesAlgebraHK +import com.github.jorgecastillo.kotlinandroid.free.algebra.HeroesAlgebraHK import com.github.jorgecastillo.kotlinandroid.di.context.SuperHeroesContext import com.github.jorgecastillo.kotlinandroid.di.context.SuperHeroesContext.GetHeroDetailsContext import com.github.jorgecastillo.kotlinandroid.di.context.SuperHeroesContext.GetHeroesContext @@ -17,7 +17,6 @@ import com.karumi.marvelapiclient.model.CharacterDto import com.karumi.marvelapiclient.model.MarvelImage import kategory.Free import kategory.HK -import kategory.binding import kategory.flatMap interface SuperHeroesView { From 84362f78837ebaa3a6ae538645fd3f490b5f07e2 Mon Sep 17 00:00:00 2001 From: Jorge Castillo Date: Fri, 6 Oct 2017 19:27:15 +0200 Subject: [PATCH 11/19] add presentation effects methods to algebra --- .../jorgecastillo/kotlinandroid/free/algebra/algebras.kt | 8 ++++++++ .../free/interpreter/AsyncResultHeroesInterpreter.kt | 2 ++ 2 files changed, 10 insertions(+) diff --git a/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/free/algebra/algebras.kt b/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/free/algebra/algebras.kt index f793c49..dc98c27 100644 --- a/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/free/algebra/algebras.kt +++ b/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/free/algebra/algebras.kt @@ -18,6 +18,8 @@ import kategory.monad class GetAll : HeroesAlgebra>() class GetSingle(val heroId: String) : HeroesAlgebra>() + class HandlePresentationErrors : HeroesAlgebra() + class DrawHeroes : HeroesAlgebra() companion object : FreeMonadInstance } @@ -36,6 +38,12 @@ fun getAllHeroes(): FreeHeroesAlgebra> = fun getSingleHero(heroId: String): FreeHeroesAlgebra> = Free.liftF(HeroesAlgebra.GetSingle(heroId)) +fun handlePresentationErrors(): FreeHeroesAlgebra = + Free.liftF(HeroesAlgebra.HandlePresentationErrors()) + +fun drawHeroes(): FreeHeroesAlgebra = + Free.liftF(HeroesAlgebra.DrawHeroes()) + /** * More complex operation using the resting operation blocks already lifted to Free. */ diff --git a/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/free/interpreter/AsyncResultHeroesInterpreter.kt b/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/free/interpreter/AsyncResultHeroesInterpreter.kt index 7d3a950..a4354dd 100644 --- a/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/free/interpreter/AsyncResultHeroesInterpreter.kt +++ b/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/free/interpreter/AsyncResultHeroesInterpreter.kt @@ -37,6 +37,8 @@ inline fun asyncResultDataSourceInterpreter( return when (op) { is HeroesAlgebra.GetAll -> getAllHeroesAsyncResult(ARM) as HK is HeroesAlgebra.GetSingle -> getHeroDetails(ARM, op.heroId) as HK + is HeroesAlgebra.HandlePresentationErrors -> TODO() + is HeroesAlgebra.DrawHeroes -> TODO() } } } From 97f14178f67b6f19893dfe392e4a356f4a1b292d Mon Sep 17 00:00:00 2001 From: Jorge Castillo Date: Fri, 6 Oct 2017 19:58:04 +0200 Subject: [PATCH 12/19] refactor to HandlePresentationEffects as an algebra op --- .../kotlinandroid/free/algebra/algebras.kt | 12 +++--- .../AsyncResultHeroesInterpreter.kt | 38 ++++++++++++++++--- .../presentation/SuperHeroesPresentation.kt | 10 +---- 3 files changed, 39 insertions(+), 21 deletions(-) diff --git a/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/free/algebra/algebras.kt b/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/free/algebra/algebras.kt index dc98c27..06c5ae4 100644 --- a/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/free/algebra/algebras.kt +++ b/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/free/algebra/algebras.kt @@ -1,6 +1,8 @@ package com.github.jorgecastillo.kotlinandroid.free.algebra +import com.github.jorgecastillo.kotlinandroid.domain.model.CharacterError import com.karumi.marvelapiclient.model.CharacterDto +import kategory.Either import kategory.Free import kategory.FreeMonadInstance import kategory.FunctionK @@ -18,8 +20,7 @@ import kategory.monad class GetAll : HeroesAlgebra>() class GetSingle(val heroId: String) : HeroesAlgebra>() - class HandlePresentationErrors : HeroesAlgebra() - class DrawHeroes : HeroesAlgebra() + class HandlePresentationEffects(val result: Either>) : HeroesAlgebra() companion object : FreeMonadInstance } @@ -38,11 +39,8 @@ fun getAllHeroes(): FreeHeroesAlgebra> = fun getSingleHero(heroId: String): FreeHeroesAlgebra> = Free.liftF(HeroesAlgebra.GetSingle(heroId)) -fun handlePresentationErrors(): FreeHeroesAlgebra = - Free.liftF(HeroesAlgebra.HandlePresentationErrors()) - -fun drawHeroes(): FreeHeroesAlgebra = - Free.liftF(HeroesAlgebra.DrawHeroes()) +fun handlePresentationEffects(result: Either>): FreeHeroesAlgebra = + Free.liftF(HeroesAlgebra.HandlePresentationEffects(result)) /** * More complex operation using the resting operation blocks already lifted to Free. diff --git a/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/free/interpreter/AsyncResultHeroesInterpreter.kt b/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/free/interpreter/AsyncResultHeroesInterpreter.kt index a4354dd..c529259 100644 --- a/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/free/interpreter/AsyncResultHeroesInterpreter.kt +++ b/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/free/interpreter/AsyncResultHeroesInterpreter.kt @@ -1,24 +1,29 @@ package com.github.jorgecastillo.kotlinandroid.free.interpreter -import com.github.jorgecastillo.kotlinandroid.free.algebra.HeroesAlgebra -import com.github.jorgecastillo.kotlinandroid.free.algebra.HeroesDataSourceAlgebraHK -import com.github.jorgecastillo.kotlinandroid.free.algebra.ev import com.github.jorgecastillo.kotlinandroid.di.context.SuperHeroesContext import com.github.jorgecastillo.kotlinandroid.domain.model.CharacterError import com.github.jorgecastillo.kotlinandroid.domain.model.CharacterError.AuthenticationError import com.github.jorgecastillo.kotlinandroid.domain.model.CharacterError.NotFoundError import com.github.jorgecastillo.kotlinandroid.domain.model.CharacterError.UnknownServerError +import com.github.jorgecastillo.kotlinandroid.domain.usecase.getHeroesUseCase +import com.github.jorgecastillo.kotlinandroid.free.algebra.HeroesAlgebra +import com.github.jorgecastillo.kotlinandroid.free.algebra.HeroesDataSourceAlgebraHK +import com.github.jorgecastillo.kotlinandroid.free.algebra.ev import com.github.jorgecastillo.kotlinandroid.functional.AsyncResult import com.github.jorgecastillo.kotlinandroid.functional.AsyncResultMonadReaderInstance import com.github.jorgecastillo.kotlinandroid.functional.ev +import com.github.jorgecastillo.kotlinandroid.view.viewmodel.SuperHeroViewModel import com.karumi.marvelapiclient.MarvelApiException import com.karumi.marvelapiclient.MarvelAuthApiException import com.karumi.marvelapiclient.model.CharacterDto import com.karumi.marvelapiclient.model.CharactersQuery.Builder +import com.karumi.marvelapiclient.model.MarvelImage.Size.PORTRAIT_UNCANNY +import kategory.Either import kategory.FunctionK import kategory.HK import kategory.Option import kategory.binding +import kategory.flatMap import java.net.HttpURLConnection /*fun test(): Unit { @@ -37,8 +42,7 @@ inline fun asyncResultDataSourceInterpreter( return when (op) { is HeroesAlgebra.GetAll -> getAllHeroesAsyncResult(ARM) as HK is HeroesAlgebra.GetSingle -> getHeroDetails(ARM, op.heroId) as HK - is HeroesAlgebra.HandlePresentationErrors -> TODO() - is HeroesAlgebra.DrawHeroes -> TODO() + is HeroesAlgebra.HandlePresentationEffects -> handlePresentationEffects(ARM, op.result) as HK } } } @@ -73,3 +77,27 @@ fun exceptionAsCharacterError(e: Throwable): CharacterError = else UnknownServerError(Option.Some(e)) else -> UnknownServerError((Option.Some(e))) } + +fun handlePresentationEffects(AR: AsyncResultMonadReaderInstance, + result: Either>): AsyncResult = + getHeroesUseCase().flatMap { it. } + AR.binding { + val ctx = AR.ask().bind() + val result = AR.handleError(getHeroesUseCase(), { displayErrors(ctx, it); emptyList() }).bind() + ctx.view.drawHeroes(result.map { + SuperHeroViewModel( + it.id, + it.name, + it.thumbnail.getImageUrl(PORTRAIT_UNCANNY), + it.description) + }) + AR.pure(Unit) + }.ev() + +fun displayErrors(ctx: SuperHeroesContext, c: CharacterError): Unit { + when (c) { + is NotFoundError -> ctx.view.showNotFoundError() + is UnknownServerError -> ctx.view.showGenericError() + is AuthenticationError -> ctx.view.showAuthenticationError() + } +} diff --git a/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/presentation/SuperHeroesPresentation.kt b/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/presentation/SuperHeroesPresentation.kt index dd557f2..e6a5332 100644 --- a/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/presentation/SuperHeroesPresentation.kt +++ b/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/presentation/SuperHeroesPresentation.kt @@ -38,15 +38,7 @@ inline fun onHeroListItemClick(heroId: String, C: MonadControl ctx.view.showNotFoundError() - is UnknownServerError -> ctx.view.showGenericError() - is AuthenticationError -> ctx.view.showAuthenticationError() - } -} - -inline fun getSuperHeroes(C: MonadControl = monadControl()): Free = +fun getSuperHeroes(): Free = getHeroesUseCase().flatMap { ctx.view.drawHeroes(result.map { SuperHeroViewModel( From 246231330179fd5137f4a881b8448bc25546fa73 Mon Sep 17 00:00:00 2001 From: Jorge Castillo Date: Fri, 6 Oct 2017 20:55:17 +0200 Subject: [PATCH 13/19] refactor and add some presentation effects --- .../kotlinandroid/context/GetHeroesContext.kt | 2 +- .../kotlinandroid/free/algebra/algebras.kt | 25 +++------- .../AsyncResultHeroesInterpreter.kt | 49 ++++++++++++------- .../presentation/SuperHeroesPresentation.kt | 23 +++++---- 4 files changed, 51 insertions(+), 48 deletions(-) diff --git a/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/context/GetHeroesContext.kt b/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/context/GetHeroesContext.kt index 897a2f5..a3c073e 100644 --- a/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/context/GetHeroesContext.kt +++ b/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/context/GetHeroesContext.kt @@ -9,7 +9,7 @@ import com.github.jorgecastillo.kotlinandroid.presentation.navigation.HeroDetail import com.karumi.marvelapiclient.CharacterApiClient import com.karumi.marvelapiclient.MarvelApiConfig.Builder -sealed class SuperHeroesContext() { +sealed class SuperHeroesContext { abstract val ctx: Context abstract val view: SuperHeroesView diff --git a/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/free/algebra/algebras.kt b/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/free/algebra/algebras.kt index 06c5ae4..9c74556 100644 --- a/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/free/algebra/algebras.kt +++ b/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/free/algebra/algebras.kt @@ -8,9 +8,9 @@ import kategory.FreeMonadInstance import kategory.FunctionK import kategory.HK import kategory.Monad +import kategory.flatMap import kategory.foldMap import kategory.higherkind -import kategory.map import kategory.monad /** @@ -18,8 +18,8 @@ import kategory.monad */ @higherkind sealed class HeroesAlgebra : HeroesAlgebraKind { - class GetAll : HeroesAlgebra>() - class GetSingle(val heroId: String) : HeroesAlgebra>() + class GetAll : HeroesAlgebra>>() + class GetSingle(val heroId: String) : HeroesAlgebra>>() class HandlePresentationEffects(val result: Either>) : HeroesAlgebra() companion object : FreeMonadInstance } @@ -33,22 +33,11 @@ inline fun Free>.run( /** * Module definition (Data Source methods). Here we lift to the Free context all the operation blocks defined on the algebra. */ -fun getAllHeroes(): FreeHeroesAlgebra> = +fun getAllHeroes(): FreeHeroesAlgebra>> = Free.liftF(HeroesAlgebra.GetAll()) -fun getSingleHero(heroId: String): FreeHeroesAlgebra> = +fun getSingleHero(heroId: String): FreeHeroesAlgebra>> = Free.liftF(HeroesAlgebra.GetSingle(heroId)) -fun handlePresentationEffects(result: Either>): FreeHeroesAlgebra = - Free.liftF(HeroesAlgebra.HandlePresentationEffects(result)) - -/** - * More complex operation using the resting operation blocks already lifted to Free. - */ -fun getAllFromAvengerComics(): FreeHeroesAlgebra> = getAllHeroes().map { - it.filter { - it.comics.items.map { it.name }.filter { - it.contains("Avenger", true) - }.count() > 0 - } -} +fun fetchAndDrawHeroes(result: Either>): FreeHeroesAlgebra = + getAllHeroes().flatMap { Free.liftF(HeroesAlgebra.HandlePresentationEffects(result)) } diff --git a/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/free/interpreter/AsyncResultHeroesInterpreter.kt b/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/free/interpreter/AsyncResultHeroesInterpreter.kt index c529259..dc1a070 100644 --- a/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/free/interpreter/AsyncResultHeroesInterpreter.kt +++ b/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/free/interpreter/AsyncResultHeroesInterpreter.kt @@ -1,11 +1,12 @@ package com.github.jorgecastillo.kotlinandroid.free.interpreter import com.github.jorgecastillo.kotlinandroid.di.context.SuperHeroesContext +import com.github.jorgecastillo.kotlinandroid.di.context.SuperHeroesContext.GetHeroDetailsContext +import com.github.jorgecastillo.kotlinandroid.di.context.SuperHeroesContext.GetHeroesContext import com.github.jorgecastillo.kotlinandroid.domain.model.CharacterError import com.github.jorgecastillo.kotlinandroid.domain.model.CharacterError.AuthenticationError import com.github.jorgecastillo.kotlinandroid.domain.model.CharacterError.NotFoundError import com.github.jorgecastillo.kotlinandroid.domain.model.CharacterError.UnknownServerError -import com.github.jorgecastillo.kotlinandroid.domain.usecase.getHeroesUseCase import com.github.jorgecastillo.kotlinandroid.free.algebra.HeroesAlgebra import com.github.jorgecastillo.kotlinandroid.free.algebra.HeroesDataSourceAlgebraHK import com.github.jorgecastillo.kotlinandroid.free.algebra.ev @@ -17,13 +18,14 @@ import com.karumi.marvelapiclient.MarvelApiException import com.karumi.marvelapiclient.MarvelAuthApiException import com.karumi.marvelapiclient.model.CharacterDto import com.karumi.marvelapiclient.model.CharactersQuery.Builder -import com.karumi.marvelapiclient.model.MarvelImage.Size.PORTRAIT_UNCANNY +import com.karumi.marvelapiclient.model.MarvelImage import kategory.Either +import kategory.Either.Left +import kategory.Either.Right import kategory.FunctionK import kategory.HK import kategory.Option import kategory.binding -import kategory.flatMap import java.net.HttpURLConnection /*fun test(): Unit { @@ -48,23 +50,23 @@ inline fun asyncResultDataSourceInterpreter( } fun getAllHeroesAsyncResult( - AR: AsyncResultMonadReaderInstance): AsyncResult> { + AR: AsyncResultMonadReaderInstance): AsyncResult>> { return AR.binding { val query = Builder.create().withOffset(0).withLimit(50).build() val ctx = AR.ask().bind() AR.catch( - { ctx.apiClient.getAll(query).response.characters.toList() }, + { Right(ctx.apiClient.getAll(query).response.characters.toList()) }, { exceptionAsCharacterError(it) } ) }.ev() } fun getHeroDetails(AR: AsyncResultMonadReaderInstance, - heroId: String): AsyncResult> = + heroId: String): AsyncResult>> = AR.binding { val ctx = AR.ask().bind() AR.catch( - { listOf(ctx.apiClient.getCharacter(heroId).response) }, + { Right(listOf(ctx.apiClient.getCharacter(heroId).response)) }, { exceptionAsCharacterError(it) } ).ev() }.ev() @@ -80,18 +82,31 @@ fun exceptionAsCharacterError(e: Throwable): CharacterError = fun handlePresentationEffects(AR: AsyncResultMonadReaderInstance, result: Either>): AsyncResult = - getHeroesUseCase().flatMap { it. } AR.binding { val ctx = AR.ask().bind() - val result = AR.handleError(getHeroesUseCase(), { displayErrors(ctx, it); emptyList() }).bind() - ctx.view.drawHeroes(result.map { - SuperHeroViewModel( - it.id, - it.name, - it.thumbnail.getImageUrl(PORTRAIT_UNCANNY), - it.description) - }) - AR.pure(Unit) + val res = when (result) { + is Left -> { + displayErrors(ctx, result.a); } + is Right -> when (ctx) { + is GetHeroesContext -> ctx.view.drawHeroes(result.b.map { + SuperHeroViewModel( + it.id, + it.name, + it.thumbnail.getImageUrl(MarvelImage.Size.PORTRAIT_UNCANNY), + it.description) + }) + is GetHeroDetailsContext -> ctx.view.drawHero(result.b.map { + SuperHeroViewModel( + it.id, + it.name, + it.thumbnail.getImageUrl(MarvelImage.Size.PORTRAIT_UNCANNY), + it.description) + }[0]) + else -> { + } + } + } + yields(Unit) }.ev() fun displayErrors(ctx: SuperHeroesContext, c: CharacterError): Unit { diff --git a/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/presentation/SuperHeroesPresentation.kt b/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/presentation/SuperHeroesPresentation.kt index e6a5332..5c092fd 100644 --- a/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/presentation/SuperHeroesPresentation.kt +++ b/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/presentation/SuperHeroesPresentation.kt @@ -38,17 +38,16 @@ inline fun onHeroListItemClick(heroId: String, C: MonadControl = - getHeroesUseCase().flatMap { - ctx.view.drawHeroes(result.map { - SuperHeroViewModel( - it.id, - it.name, - it.thumbnail.getImageUrl(MarvelImage.Size.PORTRAIT_UNCANNY), - it.description) - }) - } - /*C.binding { +fun displayErrors(ctx: SuperHeroesContext, c: CharacterError): Unit { + when (c) { + is NotFoundError -> ctx.view.showNotFoundError() + is UnknownServerError -> ctx.view.showGenericError() + is AuthenticationError -> ctx.view.showAuthenticationError() + } +} + +inline fun getSuperHeroes(C: MonadControl = monadControl()): Free = + C.binding { val ctx = C.ask().bind() val result = C.handleError(getHeroesUseCase(), { displayErrors(ctx, it); emptyList() }).bind() ctx.view.drawHeroes(result.map { @@ -59,7 +58,7 @@ fun getSuperHeroes(): Free = it.description) }) C.pure(Unit) - }*/ + } inline fun getSuperHeroDetails(heroId: String, C: MonadControl = monadControl()): HK = From 1ce811dabd1a47ecf77c13d5dfa830c36ef06dc7 Mon Sep 17 00:00:00 2001 From: Raul Raja Date: Fri, 6 Oct 2017 23:17:21 +0200 Subject: [PATCH 14/19] Free monads refactoring --- .../kotlinandroid/context/GetHeroesContext.kt | 2 - .../kotlinandroid/data/HeroesRepository.kt | 23 ++-- .../domain/usecase/HeroesUseCases.kt | 2 +- .../kotlinandroid/free/algebra/algebras.kt | 41 +++--- .../AsyncResultHeroesInterpreter.kt | 118 ------------------ .../free/interpreter/Interpreter.kt | 102 +++++++++++++++ .../presentation/SuperHeroesPresentation.kt | 68 +++------- .../presentation/navigation/Navigation.kt | 15 --- .../view/SuperHeroDetailActivity.kt | 11 +- .../view/SuperHeroListActivity.kt | 27 ++-- .../kotlinandroid/ExampleUnitTest.java | 16 --- 11 files changed, 163 insertions(+), 262 deletions(-) delete mode 100644 free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/free/interpreter/AsyncResultHeroesInterpreter.kt create mode 100644 free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/free/interpreter/Interpreter.kt delete mode 100644 free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/presentation/navigation/Navigation.kt delete mode 100644 free-monads/src/test/java/com/github/jorgecastillo/kotlinandroid/ExampleUnitTest.java diff --git a/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/context/GetHeroesContext.kt b/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/context/GetHeroesContext.kt index a3c073e..7918406 100644 --- a/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/context/GetHeroesContext.kt +++ b/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/context/GetHeroesContext.kt @@ -5,7 +5,6 @@ import com.github.jorgecastillo.kotlinandroid.BuildConfig import com.github.jorgecastillo.kotlinandroid.presentation.SuperHeroDetailView import com.github.jorgecastillo.kotlinandroid.presentation.SuperHeroesListView import com.github.jorgecastillo.kotlinandroid.presentation.SuperHeroesView -import com.github.jorgecastillo.kotlinandroid.presentation.navigation.HeroDetailsPage import com.karumi.marvelapiclient.CharacterApiClient import com.karumi.marvelapiclient.MarvelApiConfig.Builder @@ -14,7 +13,6 @@ sealed class SuperHeroesContext { abstract val ctx: Context abstract val view: SuperHeroesView - val heroDetailsPage = HeroDetailsPage() val apiClient get() = CharacterApiClient(Builder( BuildConfig.MARVEL_PUBLIC_KEY, diff --git a/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/data/HeroesRepository.kt b/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/data/HeroesRepository.kt index 43e9b10..255078c 100644 --- a/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/data/HeroesRepository.kt +++ b/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/data/HeroesRepository.kt @@ -5,7 +5,6 @@ import com.github.jorgecastillo.kotlinandroid.data.CachePolicy.LocalOnly import com.github.jorgecastillo.kotlinandroid.data.CachePolicy.NetworkFirst import com.github.jorgecastillo.kotlinandroid.data.CachePolicy.NetworkOnly import com.github.jorgecastillo.kotlinandroid.free.algebra.FreeHeroesAlgebra -import com.github.jorgecastillo.kotlinandroid.free.algebra.getAllFromAvengerComics import com.github.jorgecastillo.kotlinandroid.free.algebra.getAllHeroes import com.github.jorgecastillo.kotlinandroid.free.algebra.getSingleHero import com.karumi.marvelapiclient.model.CharacterDto @@ -20,23 +19,17 @@ sealed class CachePolicy { fun getHeroesWithCachePolicy(policy: CachePolicy): FreeHeroesAlgebra> = when (policy) { is NetworkOnly -> getAllHeroes() - is NetworkFirst -> getAllHeroes() // TODO change to conditional call - is LocalOnly -> getAllHeroes() // TODO change to local only cache call - is LocalFirst -> getAllHeroes() // TODO change to conditional call + is NetworkFirst -> getAllHeroes() + is LocalOnly -> getAllHeroes() + is LocalFirst -> getAllHeroes() } -fun getHeroDetails(policy: CachePolicy, heroId: String): FreeHeroesAlgebra> = +fun getHeroDetails(policy: CachePolicy, heroId: String): FreeHeroesAlgebra = when (policy) { is NetworkOnly -> getSingleHero(heroId) - is NetworkFirst -> getSingleHero(heroId) // TODO change to conditional call - is LocalOnly -> getSingleHero(heroId) // TODO change to local only cache call - is LocalFirst -> getSingleHero(heroId) // TODO change to conditional call + is NetworkFirst -> getSingleHero(heroId) + is LocalOnly -> getSingleHero(heroId) + is LocalFirst -> getSingleHero(heroId) } -fun getHeroesFromAvengerComicsWithCachePolicy(policy: CachePolicy): FreeHeroesAlgebra> = - when (policy) { - is NetworkOnly -> getAllFromAvengerComics() - is NetworkFirst -> getAllFromAvengerComics() // TODO change to conditional call - is LocalOnly -> getAllFromAvengerComics() // TODO change to local only cache call - is LocalFirst -> getAllFromAvengerComics() // TODO change to conditional call - } +fun getHeroesFromAvengerComicsWithCachePolicy(policy: CachePolicy): FreeHeroesAlgebra> = TODO() diff --git a/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/domain/usecase/HeroesUseCases.kt b/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/domain/usecase/HeroesUseCases.kt index f40a0a2..b7a8dfe 100644 --- a/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/domain/usecase/HeroesUseCases.kt +++ b/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/domain/usecase/HeroesUseCases.kt @@ -10,7 +10,7 @@ import com.karumi.marvelapiclient.model.CharacterDto fun getHeroesUseCase(): FreeHeroesAlgebra> = getHeroesWithCachePolicy(NetworkOnly) -fun getHeroDetailsUseCase(heroId: String): FreeHeroesAlgebra> = +fun getHeroDetailsUseCase(heroId: String): FreeHeroesAlgebra = getHeroDetails(NetworkOnly, heroId) fun getHeroesFromAvengerComicsUseCase(): FreeHeroesAlgebra> = diff --git a/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/free/algebra/algebras.kt b/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/free/algebra/algebras.kt index 9c74556..c79f327 100644 --- a/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/free/algebra/algebras.kt +++ b/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/free/algebra/algebras.kt @@ -2,42 +2,37 @@ package com.github.jorgecastillo.kotlinandroid.free.algebra import com.github.jorgecastillo.kotlinandroid.domain.model.CharacterError import com.karumi.marvelapiclient.model.CharacterDto -import kategory.Either -import kategory.Free -import kategory.FreeMonadInstance -import kategory.FunctionK -import kategory.HK -import kategory.Monad -import kategory.flatMap -import kategory.foldMap -import kategory.higherkind -import kategory.monad +import kategory.* /** * Algebra for Hero data sources. Algebras are defined by a sealed class (ADT) with a limited amount of implementations reflecting the operations available. */ @higherkind sealed class HeroesAlgebra : HeroesAlgebraKind { - - class GetAll : HeroesAlgebra>>() - class GetSingle(val heroId: String) : HeroesAlgebra>>() - class HandlePresentationEffects(val result: Either>) : HeroesAlgebra() - companion object : FreeMonadInstance + object GetAll : HeroesAlgebra>() + class GetSingle(val heroId: String) : HeroesAlgebra() + class HandlePresentationEffects(val result: Either>) : HeroesAlgebra() + class Attempt(val fa: FreeHeroesAlgebra): HeroesAlgebra>() + companion object : FreeMonadInstance } typealias FreeHeroesAlgebra = Free inline fun Free>.run( - interpreter: FunctionK, MF: Monad = monad()): HK> = - this.foldMap(interpreter, MF) + interpreter: FunctionK, MF: Monad = monad()): HK> = + this.foldMap(interpreter, MF) /** * Module definition (Data Source methods). Here we lift to the Free context all the operation blocks defined on the algebra. */ -fun getAllHeroes(): FreeHeroesAlgebra>> = - Free.liftF(HeroesAlgebra.GetAll()) +fun getAllHeroes(): FreeHeroesAlgebra> = + Free.liftF(HeroesAlgebra.GetAll) + +fun getSingleHero(heroId: String): FreeHeroesAlgebra = + Free.liftF(HeroesAlgebra.GetSingle(heroId)) + +fun handlePresentationEffects(result: Either>): FreeHeroesAlgebra = + Free.liftF(HeroesAlgebra.HandlePresentationEffects(result)) -fun getSingleHero(heroId: String): FreeHeroesAlgebra>> = - Free.liftF(HeroesAlgebra.GetSingle(heroId)) +fun attempt(fa: FreeHeroesAlgebra): FreeHeroesAlgebra> = + Free.liftF(HeroesAlgebra.Attempt(fa)) -fun fetchAndDrawHeroes(result: Either>): FreeHeroesAlgebra = - getAllHeroes().flatMap { Free.liftF(HeroesAlgebra.HandlePresentationEffects(result)) } diff --git a/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/free/interpreter/AsyncResultHeroesInterpreter.kt b/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/free/interpreter/AsyncResultHeroesInterpreter.kt deleted file mode 100644 index dc1a070..0000000 --- a/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/free/interpreter/AsyncResultHeroesInterpreter.kt +++ /dev/null @@ -1,118 +0,0 @@ -package com.github.jorgecastillo.kotlinandroid.free.interpreter - -import com.github.jorgecastillo.kotlinandroid.di.context.SuperHeroesContext -import com.github.jorgecastillo.kotlinandroid.di.context.SuperHeroesContext.GetHeroDetailsContext -import com.github.jorgecastillo.kotlinandroid.di.context.SuperHeroesContext.GetHeroesContext -import com.github.jorgecastillo.kotlinandroid.domain.model.CharacterError -import com.github.jorgecastillo.kotlinandroid.domain.model.CharacterError.AuthenticationError -import com.github.jorgecastillo.kotlinandroid.domain.model.CharacterError.NotFoundError -import com.github.jorgecastillo.kotlinandroid.domain.model.CharacterError.UnknownServerError -import com.github.jorgecastillo.kotlinandroid.free.algebra.HeroesAlgebra -import com.github.jorgecastillo.kotlinandroid.free.algebra.HeroesDataSourceAlgebraHK -import com.github.jorgecastillo.kotlinandroid.free.algebra.ev -import com.github.jorgecastillo.kotlinandroid.functional.AsyncResult -import com.github.jorgecastillo.kotlinandroid.functional.AsyncResultMonadReaderInstance -import com.github.jorgecastillo.kotlinandroid.functional.ev -import com.github.jorgecastillo.kotlinandroid.view.viewmodel.SuperHeroViewModel -import com.karumi.marvelapiclient.MarvelApiException -import com.karumi.marvelapiclient.MarvelAuthApiException -import com.karumi.marvelapiclient.model.CharacterDto -import com.karumi.marvelapiclient.model.CharactersQuery.Builder -import com.karumi.marvelapiclient.model.MarvelImage -import kategory.Either -import kategory.Either.Left -import kategory.Either.Right -import kategory.FunctionK -import kategory.HK -import kategory.Option -import kategory.binding -import java.net.HttpURLConnection - -/*fun test(): Unit { - val heroesDS = object : HeroesDataSource { - } - - val MR = AsyncResult.monadReader() - heroesDS.getAll().foldMap(asyncResultDataSourceInterpreter(MR), MR) -}*/ - -inline fun asyncResultDataSourceInterpreter( - ARM: AsyncResultMonadReaderInstance): FunctionK = - object : FunctionK { - override fun invoke(fa: HK): HK { - val op = fa.ev() - return when (op) { - is HeroesAlgebra.GetAll -> getAllHeroesAsyncResult(ARM) as HK - is HeroesAlgebra.GetSingle -> getHeroDetails(ARM, op.heroId) as HK - is HeroesAlgebra.HandlePresentationEffects -> handlePresentationEffects(ARM, op.result) as HK - } - } - } - -fun getAllHeroesAsyncResult( - AR: AsyncResultMonadReaderInstance): AsyncResult>> { - return AR.binding { - val query = Builder.create().withOffset(0).withLimit(50).build() - val ctx = AR.ask().bind() - AR.catch( - { Right(ctx.apiClient.getAll(query).response.characters.toList()) }, - { exceptionAsCharacterError(it) } - ) - }.ev() -} - -fun getHeroDetails(AR: AsyncResultMonadReaderInstance, - heroId: String): AsyncResult>> = - AR.binding { - val ctx = AR.ask().bind() - AR.catch( - { Right(listOf(ctx.apiClient.getCharacter(heroId).response)) }, - { exceptionAsCharacterError(it) } - ).ev() - }.ev() - -fun exceptionAsCharacterError(e: Throwable): CharacterError = - when (e) { - is MarvelAuthApiException -> AuthenticationError - is MarvelApiException -> - if (e.httpCode == HttpURLConnection.HTTP_NOT_FOUND) NotFoundError - else UnknownServerError(Option.Some(e)) - else -> UnknownServerError((Option.Some(e))) - } - -fun handlePresentationEffects(AR: AsyncResultMonadReaderInstance, - result: Either>): AsyncResult = - AR.binding { - val ctx = AR.ask().bind() - val res = when (result) { - is Left -> { - displayErrors(ctx, result.a); } - is Right -> when (ctx) { - is GetHeroesContext -> ctx.view.drawHeroes(result.b.map { - SuperHeroViewModel( - it.id, - it.name, - it.thumbnail.getImageUrl(MarvelImage.Size.PORTRAIT_UNCANNY), - it.description) - }) - is GetHeroDetailsContext -> ctx.view.drawHero(result.b.map { - SuperHeroViewModel( - it.id, - it.name, - it.thumbnail.getImageUrl(MarvelImage.Size.PORTRAIT_UNCANNY), - it.description) - }[0]) - else -> { - } - } - } - yields(Unit) - }.ev() - -fun displayErrors(ctx: SuperHeroesContext, c: CharacterError): Unit { - when (c) { - is NotFoundError -> ctx.view.showNotFoundError() - is UnknownServerError -> ctx.view.showGenericError() - is AuthenticationError -> ctx.view.showAuthenticationError() - } -} diff --git a/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/free/interpreter/Interpreter.kt b/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/free/interpreter/Interpreter.kt new file mode 100644 index 0000000..4ec83af --- /dev/null +++ b/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/free/interpreter/Interpreter.kt @@ -0,0 +1,102 @@ +package com.github.jorgecastillo.kotlinandroid.free.interpreter + +import com.github.jorgecastillo.kotlinandroid.di.context.SuperHeroesContext +import com.github.jorgecastillo.kotlinandroid.di.context.SuperHeroesContext.GetHeroDetailsContext +import com.github.jorgecastillo.kotlinandroid.di.context.SuperHeroesContext.GetHeroesContext +import com.github.jorgecastillo.kotlinandroid.domain.model.CharacterError +import com.github.jorgecastillo.kotlinandroid.domain.model.CharacterError.* +import com.github.jorgecastillo.kotlinandroid.free.algebra.* +import com.github.jorgecastillo.kotlinandroid.view.viewmodel.SuperHeroViewModel +import com.karumi.marvelapiclient.MarvelApiException +import com.karumi.marvelapiclient.MarvelAuthApiException +import com.karumi.marvelapiclient.model.CharacterDto +import com.karumi.marvelapiclient.model.CharactersQuery.Builder +import com.karumi.marvelapiclient.model.MarvelImage +import kategory.* +import kategory.Either.Left +import kategory.Either.Right +import java.net.HttpURLConnection + +@Suppress("UNCHECKED_CAST") +fun interpreter(ctx: SuperHeroesContext, ME: MonadError): FunctionK = + object : FunctionK { + override fun invoke(fa: HeroesAlgebraKind): HK { + val op = fa.ev() + return when (op) { + is HeroesAlgebra.GetAll -> getAllHeroesImpl(ctx, ME) + is HeroesAlgebra.GetSingle -> getHeroDetailsImpl(ctx, ME, op.heroId) + is HeroesAlgebra.HandlePresentationEffects -> ME.catch( + { handlePresentationEffects(ctx, op.result) }, + { exceptionAsCharacterError(it) } + ) + is HeroesAlgebra.Attempt<*> -> { + val result = op.fa.foldMap(interpreter(ctx, ME), ME) + ME.attempt(result) + } + } as HK + } + } + +fun getAllHeroesImpl(ctx: SuperHeroesContext, ME: MonadError): HK> { + return ME.binding { + val query = Builder.create().withOffset(0).withLimit(50).build() + ME.catch( + { ctx.apiClient.getAll(query).response.characters.toList() }, + { exceptionAsCharacterError(it) } + ) + } +} + +fun getHeroDetailsImpl( + ctx: SuperHeroesContext, + ME: MonadError, + heroId: String): HK = + ME.binding { + ME.catch( + { ctx.apiClient.getCharacter(heroId).response }, + { exceptionAsCharacterError(it) } + ) + } + +fun exceptionAsCharacterError(e: Throwable): CharacterError = + when (e) { + is MarvelAuthApiException -> AuthenticationError + is MarvelApiException -> + if (e.httpCode == HttpURLConnection.HTTP_NOT_FOUND) NotFoundError + else UnknownServerError(Option.Some(e)) + else -> UnknownServerError((Option.Some(e))) + } + +fun handlePresentationEffects( + ctx: SuperHeroesContext, + result: Either>): Unit = + when (result) { + is Left -> { + displayErrors(ctx, result.a); } + is Right -> when (ctx) { + is GetHeroesContext -> ctx.view.drawHeroes(result.b.map { + SuperHeroViewModel( + it.id, + it.name, + it.thumbnail.getImageUrl(MarvelImage.Size.PORTRAIT_UNCANNY), + it.description) + }) + is GetHeroDetailsContext -> ctx.view.drawHero(result.b.map { + SuperHeroViewModel( + it.id, + it.name, + it.thumbnail.getImageUrl(MarvelImage.Size.PORTRAIT_UNCANNY), + it.description) + }[0]) + } + } + +fun displayErrors(ctx: SuperHeroesContext, c: CharacterError): Unit { + when (c) { + is NotFoundError -> ctx.view.showNotFoundError() + is UnknownServerError -> ctx.view.showGenericError() + is AuthenticationError -> ctx.view.showAuthenticationError() + } +} + + diff --git a/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/presentation/SuperHeroesPresentation.kt b/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/presentation/SuperHeroesPresentation.kt index 5c092fd..a1ad858 100644 --- a/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/presentation/SuperHeroesPresentation.kt +++ b/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/presentation/SuperHeroesPresentation.kt @@ -1,23 +1,12 @@ package com.github.jorgecastillo.kotlinandroid.presentation -import com.github.jorgecastillo.kotlinandroid.free.algebra.HeroesAlgebraHK -import com.github.jorgecastillo.kotlinandroid.di.context.SuperHeroesContext -import com.github.jorgecastillo.kotlinandroid.di.context.SuperHeroesContext.GetHeroDetailsContext -import com.github.jorgecastillo.kotlinandroid.di.context.SuperHeroesContext.GetHeroesContext import com.github.jorgecastillo.kotlinandroid.domain.model.CharacterError -import com.github.jorgecastillo.kotlinandroid.domain.model.CharacterError.AuthenticationError -import com.github.jorgecastillo.kotlinandroid.domain.model.CharacterError.NotFoundError -import com.github.jorgecastillo.kotlinandroid.domain.model.CharacterError.UnknownServerError -import com.github.jorgecastillo.kotlinandroid.domain.usecase.getHeroDetailsUseCase -import com.github.jorgecastillo.kotlinandroid.domain.usecase.getHeroesUseCase -import com.github.jorgecastillo.kotlinandroid.functional.MonadControl -import com.github.jorgecastillo.kotlinandroid.functional.monadControl +import com.github.jorgecastillo.kotlinandroid.free.algebra.* import com.github.jorgecastillo.kotlinandroid.view.viewmodel.SuperHeroViewModel import com.karumi.marvelapiclient.model.CharacterDto -import com.karumi.marvelapiclient.model.MarvelImage -import kategory.Free -import kategory.HK -import kategory.flatMap +import kategory.Either +import kategory.binding +import kategory.ev interface SuperHeroesView { fun showNotFoundError() @@ -33,42 +22,17 @@ interface SuperHeroDetailView : SuperHeroesView { fun drawHero(hero: SuperHeroViewModel) } -inline fun onHeroListItemClick(heroId: String, C: MonadControl) = - C.ask().flatMap(C, { - it.heroDetailsPage.go(heroId, C) - }) +fun showSuperHeroes(): FreeHeroesAlgebra = + HeroesAlgebra.binding { + val res: Either> = attempt(getAllHeroes()).bind() + val efRes = handlePresentationEffects(res).bind() + yields(efRes) + }.ev() -fun displayErrors(ctx: SuperHeroesContext, c: CharacterError): Unit { - when (c) { - is NotFoundError -> ctx.view.showNotFoundError() - is UnknownServerError -> ctx.view.showGenericError() - is AuthenticationError -> ctx.view.showAuthenticationError() - } -} - -inline fun getSuperHeroes(C: MonadControl = monadControl()): Free = - C.binding { - val ctx = C.ask().bind() - val result = C.handleError(getHeroesUseCase(), { displayErrors(ctx, it); emptyList() }).bind() - ctx.view.drawHeroes(result.map { - SuperHeroViewModel( - it.id, - it.name, - it.thumbnail.getImageUrl(MarvelImage.Size.PORTRAIT_UNCANNY), - it.description) - }) - C.pure(Unit) - } +fun showSuperHeroDetail(heroId: String): FreeHeroesAlgebra = + HeroesAlgebra.binding { + val res: Either = attempt(getSingleHero(heroId)).bind() + val efRes = handlePresentationEffects(res.map(::listOf)).bind() + yields(efRes) + }.ev() -inline fun getSuperHeroDetails(heroId: String, - C: MonadControl = monadControl()): HK = - C.binding { - val ctx = C.ask().bind() - val result = C.handleError(getHeroDetailsUseCase(heroId), { displayErrors(ctx, it); CharacterDto() }).bind() - ctx.view.drawHero(SuperHeroViewModel( - result.id, - result.name, - result.thumbnail.getImageUrl(MarvelImage.Size.PORTRAIT_UNCANNY), - result.description)) - C.pure(Unit) - } diff --git a/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/presentation/navigation/Navigation.kt b/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/presentation/navigation/Navigation.kt deleted file mode 100644 index 72491b4..0000000 --- a/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/presentation/navigation/Navigation.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.github.jorgecastillo.kotlinandroid.presentation.navigation - -import com.github.jorgecastillo.kotlinandroid.di.context.SuperHeroesContext.GetHeroesContext -import com.github.jorgecastillo.kotlinandroid.domain.model.CharacterError -import com.github.jorgecastillo.kotlinandroid.functional.MonadControl -import com.github.jorgecastillo.kotlinandroid.view.SuperHeroDetailActivity -import kategory.map - -class HeroDetailsPage { - - inline fun go(heroId: String, C: MonadControl) = - C.ask().map(C, { - SuperHeroDetailActivity.launch(it.ctx, heroId) - }) -} diff --git a/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/view/SuperHeroDetailActivity.kt b/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/view/SuperHeroDetailActivity.kt index 2c62a81..65cb046 100644 --- a/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/view/SuperHeroDetailActivity.kt +++ b/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/view/SuperHeroDetailActivity.kt @@ -9,15 +9,10 @@ import android.widget.Toast import com.github.jorgecastillo.kotlinandroid.R import com.github.jorgecastillo.kotlinandroid.R.string import com.github.jorgecastillo.kotlinandroid.di.context.SuperHeroesContext.GetHeroDetailsContext -import com.github.jorgecastillo.kotlinandroid.functional.AsyncResult -import com.github.jorgecastillo.kotlinandroid.functional.ev import com.github.jorgecastillo.kotlinandroid.presentation.SuperHeroDetailView -import com.github.jorgecastillo.kotlinandroid.presentation.getSuperHeroDetails +import com.github.jorgecastillo.kotlinandroid.presentation.showSuperHeroDetail import com.github.jorgecastillo.kotlinandroid.view.viewmodel.SuperHeroViewModel -import kotlinx.android.synthetic.main.activity_detail.appBar -import kotlinx.android.synthetic.main.activity_detail.collapsingToolbar -import kotlinx.android.synthetic.main.activity_detail.description -import kotlinx.android.synthetic.main.activity_detail.headerImage +import kotlinx.android.synthetic.main.activity_detail.* class SuperHeroDetailActivity : AppCompatActivity(), SuperHeroDetailView { @@ -40,7 +35,7 @@ class SuperHeroDetailActivity : AppCompatActivity(), SuperHeroDetailView { super.onResume() intent.extras?.let { val heroId = it.getString(EXTRA_HERO_ID) - getSuperHeroDetails(heroId, AsyncResult()).ev().run(GetHeroDetailsContext(this, this)) + showSuperHeroDetail(heroId).unsafePerformEffects(GetHeroDetailsContext(this, this)) } ?: closeWithError() } diff --git a/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/view/SuperHeroListActivity.kt b/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/view/SuperHeroListActivity.kt index 2b1cd9f..eb159ab 100644 --- a/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/view/SuperHeroListActivity.kt +++ b/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/view/SuperHeroListActivity.kt @@ -5,45 +5,42 @@ import android.support.design.widget.Snackbar import android.support.v7.app.AppCompatActivity import android.support.v7.widget.LinearLayoutManager import com.github.jorgecastillo.kotlinandroid.R +import com.github.jorgecastillo.kotlinandroid.di.context.SuperHeroesContext import com.github.jorgecastillo.kotlinandroid.di.context.SuperHeroesContext.GetHeroesContext +import com.github.jorgecastillo.kotlinandroid.free.algebra.FreeHeroesAlgebra +import com.github.jorgecastillo.kotlinandroid.free.interpreter.interpreter import com.github.jorgecastillo.kotlinandroid.functional.AsyncResult import com.github.jorgecastillo.kotlinandroid.functional.ev -import com.github.jorgecastillo.kotlinandroid.functional.monadControl +import com.github.jorgecastillo.kotlinandroid.functional.monadError import com.github.jorgecastillo.kotlinandroid.presentation.SuperHeroesListView -import com.github.jorgecastillo.kotlinandroid.presentation.getSuperHeroes -import com.github.jorgecastillo.kotlinandroid.presentation.onHeroListItemClick +import com.github.jorgecastillo.kotlinandroid.presentation.showSuperHeroes import com.github.jorgecastillo.kotlinandroid.view.adapter.HeroesCardAdapter import com.github.jorgecastillo.kotlinandroid.view.viewmodel.SuperHeroViewModel -import kotlinx.android.synthetic.main.activity_main.heroesList +import kategory.foldMap +import kotlinx.android.synthetic.main.activity_main.* class SuperHeroListActivity : AppCompatActivity(), SuperHeroesListView { private lateinit var adapter: HeroesCardAdapter - private lateinit var heroesContext: GetHeroesContext override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) - setupDependencyGraph() setupList() } - private fun setupDependencyGraph() { - heroesContext = GetHeroesContext(this, this) - } - private fun setupList() { heroesList.setHasFixedSize(true) heroesList.layoutManager = LinearLayoutManager(this) adapter = HeroesCardAdapter(itemClick = { - onHeroListItemClick(it.heroId, AsyncResult()).ev().run(heroesContext) + SuperHeroDetailActivity.launch(this, it.heroId) }) heroesList.adapter = adapter } override fun onResume() { super.onResume() - getSuperHeroes(AsyncResult()).ev().run(GetHeroesContext(this, this)) + showSuperHeroes().unsafePerformEffects(GetHeroesContext(this, this)) } override fun drawHeroes(heroes: List) = runOnUiThread { @@ -63,3 +60,9 @@ class SuperHeroListActivity : AppCompatActivity(), SuperHeroesListView { Snackbar.make(heroesList, R.string.authentication, Snackbar.LENGTH_SHORT).show() } } + + +inline fun FreeHeroesAlgebra.unsafePerformEffects(ctx: SuperHeroesContext): AsyncResult { + val ME = AsyncResult.monadError() + return this.foldMap(interpreter(ctx, ME), ME).ev() +} \ No newline at end of file diff --git a/free-monads/src/test/java/com/github/jorgecastillo/kotlinandroid/ExampleUnitTest.java b/free-monads/src/test/java/com/github/jorgecastillo/kotlinandroid/ExampleUnitTest.java deleted file mode 100644 index 0bcebf3..0000000 --- a/free-monads/src/test/java/com/github/jorgecastillo/kotlinandroid/ExampleUnitTest.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.github.jorgecastillo.kotlinandroid; - -import org.junit.Test; - -import static org.junit.Assert.*; - -/** - * Example local unit test, which will execute on the development machine (host). - * - * @see Testing documentation - */ -public class ExampleUnitTest { - @Test public void addition_isCorrect() throws Exception { - assertEquals(4, 2 + 2); - } -} From 9f43b4fae2fcd7c12328e44187b40f203cdf8784 Mon Sep 17 00:00:00 2001 From: Raul Raja Date: Sat, 7 Oct 2017 03:06:15 +0200 Subject: [PATCH 15/19] Interpret to IO --- free-monads/build.gradle | 1 + .../kotlinandroid/free/algebra/algebras.kt | 4 +- .../free/interpreter/Interpreter.kt | 77 ++++---- .../kotlinandroid/functional/AsyncResult.kt | 173 ------------------ .../kotlinandroid/functional/Future.kt | 53 ------ .../presentation/SuperHeroesPresentation.kt | 24 ++- .../view/SuperHeroListActivity.kt | 11 +- 7 files changed, 69 insertions(+), 274 deletions(-) delete mode 100644 free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/functional/AsyncResult.kt delete mode 100644 free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/functional/Future.kt diff --git a/free-monads/build.gradle b/free-monads/build.gradle index 020cfe0..f9dc00d 100644 --- a/free-monads/build.gradle +++ b/free-monads/build.gradle @@ -79,5 +79,6 @@ dependencies { compile "com.squareup.picasso:picasso:2.5.2" compile "io.kategory:kategory-annotations:$kategory_version" compile "io.kategory:kategory:$kategory_version" + compile "io.kategory:kategory-effects:$kategory_version" compile 'com.karumi:marvelapiclient:1.0.1' } diff --git a/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/free/algebra/algebras.kt b/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/free/algebra/algebras.kt index c79f327..682e431 100644 --- a/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/free/algebra/algebras.kt +++ b/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/free/algebra/algebras.kt @@ -11,7 +11,7 @@ import kategory.* object GetAll : HeroesAlgebra>() class GetSingle(val heroId: String) : HeroesAlgebra() class HandlePresentationEffects(val result: Either>) : HeroesAlgebra() - class Attempt(val fa: FreeHeroesAlgebra): HeroesAlgebra>() + class Attempt(val fa: FreeHeroesAlgebra): HeroesAlgebra>() companion object : FreeMonadInstance } @@ -33,6 +33,6 @@ fun getSingleHero(heroId: String): FreeHeroesAlgebra = fun handlePresentationEffects(result: Either>): FreeHeroesAlgebra = Free.liftF(HeroesAlgebra.HandlePresentationEffects(result)) -fun attempt(fa: FreeHeroesAlgebra): FreeHeroesAlgebra> = +fun attempt(fa: FreeHeroesAlgebra): FreeHeroesAlgebra> = Free.liftF(HeroesAlgebra.Attempt(fa)) diff --git a/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/free/interpreter/Interpreter.kt b/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/free/interpreter/Interpreter.kt index 4ec83af..f9b1f5e 100644 --- a/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/free/interpreter/Interpreter.kt +++ b/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/free/interpreter/Interpreter.kt @@ -7,67 +7,78 @@ import com.github.jorgecastillo.kotlinandroid.domain.model.CharacterError import com.github.jorgecastillo.kotlinandroid.domain.model.CharacterError.* import com.github.jorgecastillo.kotlinandroid.free.algebra.* import com.github.jorgecastillo.kotlinandroid.view.viewmodel.SuperHeroViewModel -import com.karumi.marvelapiclient.MarvelApiException -import com.karumi.marvelapiclient.MarvelAuthApiException import com.karumi.marvelapiclient.model.CharacterDto import com.karumi.marvelapiclient.model.CharactersQuery.Builder import com.karumi.marvelapiclient.model.MarvelImage import kategory.* import kategory.Either.Left import kategory.Either.Right -import java.net.HttpURLConnection +import kategory.effects.AsyncContext +import kotlinx.coroutines.experimental.CommonPool +import kotlinx.coroutines.experimental.async +import kotlinx.coroutines.experimental.runBlocking @Suppress("UNCHECKED_CAST") -fun interpreter(ctx: SuperHeroesContext, ME: MonadError): FunctionK = +fun interpreter(ctx: SuperHeroesContext, ME: MonadError, AC: AsyncContext): FunctionK = object : FunctionK { override fun invoke(fa: HeroesAlgebraKind): HK { val op = fa.ev() return when (op) { - is HeroesAlgebra.GetAll -> getAllHeroesImpl(ctx, ME) - is HeroesAlgebra.GetSingle -> getHeroDetailsImpl(ctx, ME, op.heroId) - is HeroesAlgebra.HandlePresentationEffects -> ME.catch( - { handlePresentationEffects(ctx, op.result) }, - { exceptionAsCharacterError(it) } - ) + is HeroesAlgebra.GetAll -> getAllHeroesImpl(ctx, ME, AC) + is HeroesAlgebra.GetSingle -> getHeroDetailsImpl(ctx, ME, AC, op.heroId) + is HeroesAlgebra.HandlePresentationEffects -> { + ME.catch({ handlePresentationEffectsImpl(ctx, op.result) }) + } is HeroesAlgebra.Attempt<*> -> { - val result = op.fa.foldMap(interpreter(ctx, ME), ME) + val result = op.fa.foldMap(interpreter(ctx, ME, AC), ME) ME.attempt(result) } } as HK } } -fun getAllHeroesImpl(ctx: SuperHeroesContext, ME: MonadError): HK> { - return ME.binding { +private fun runInAsyncContext( + f: () -> A, + onError: (Throwable) -> B, + onSuccess: (A) -> B, AC: AsyncContext): HK { + return AC.runAsync { proc -> + async(CommonPool) { + val result = Try { f() }.fold(onError, onSuccess) + proc(result.right()) + } + } +} + +fun getAllHeroesImpl( + ctx: SuperHeroesContext, + ME: MonadError, + AC: AsyncContext): HK> { + return ME.bindingE { val query = Builder.create().withOffset(0).withLimit(50).build() - ME.catch( - { ctx.apiClient.getAll(query).response.characters.toList() }, - { exceptionAsCharacterError(it) } - ) + runInAsyncContext( + f = {ctx.apiClient.getAll(query).response.characters.toList()}, + onError = { ME.raiseError>(it) }, + onSuccess = { ME.pure(it) }, + AC = AC + ).bind() } } fun getHeroDetailsImpl( ctx: SuperHeroesContext, - ME: MonadError, + ME: MonadError, + AC: AsyncContext, heroId: String): HK = - ME.binding { - ME.catch( - { ctx.apiClient.getCharacter(heroId).response }, - { exceptionAsCharacterError(it) } - ) - } - -fun exceptionAsCharacterError(e: Throwable): CharacterError = - when (e) { - is MarvelAuthApiException -> AuthenticationError - is MarvelApiException -> - if (e.httpCode == HttpURLConnection.HTTP_NOT_FOUND) NotFoundError - else UnknownServerError(Option.Some(e)) - else -> UnknownServerError((Option.Some(e))) + ME.bindingE { + runInAsyncContext( + f = { ctx.apiClient.getCharacter(heroId).response }, + onError = { ME.raiseError(it) }, + onSuccess = { ME.pure(it) }, + AC = AC + ).bind() } -fun handlePresentationEffects( +fun handlePresentationEffectsImpl( ctx: SuperHeroesContext, result: Either>): Unit = when (result) { diff --git a/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/functional/AsyncResult.kt b/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/functional/AsyncResult.kt deleted file mode 100644 index 58793a9..0000000 --- a/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/functional/AsyncResult.kt +++ /dev/null @@ -1,173 +0,0 @@ -package com.github.jorgecastillo.kotlinandroid.functional - -import com.github.jorgecastillo.kotlinandroid.di.context.SuperHeroesContext -import com.github.jorgecastillo.kotlinandroid.domain.model.CharacterError -import com.github.jorgecastillo.kotlinandroid.functional.AsyncResult.Companion.KMR -import kategory.Applicative -import kategory.Either -import kategory.EitherT -import kategory.EitherTKindPartial -import kategory.Functor -import kategory.HK -import kategory.Kleisli -import kategory.KleisliMonadErrorInstance -import kategory.KleisliMonadErrorInstanceImplicits -import kategory.KleisliMonadReaderInstance -import kategory.KleisliMonadReaderInstanceImplicits -import kategory.Monad -import kategory.MonadError -import kategory.MonadReader -import kategory.andThen -import kategory.ev - -typealias Result = Kleisli, D, A> - -fun Result.asyncResult(): AsyncResult = AsyncResult(this) - -class AsyncResultHK private constructor() - -typealias AsyncResultKind = kategory.HK2 - -typealias AsyncResultKindPartial = kategory.HK -@Suppress("UNCHECKED_CAST") -inline fun AsyncResultKind.ev(): AsyncResult = - this as AsyncResult - -class AsyncResult(val value: Result) : AsyncResultKind { - - fun map(f: (A) -> B): AsyncResult = - value.map(f, ETM()).asyncResult() - - fun flatMap(f: (A) -> AsyncResultKind): AsyncResult = - value.flatMap(f.andThen { it.ev().value }, ETM()).asyncResult() - - fun ap(ff: AsyncResultKind B>): AsyncResult = ff.ev().flatMap { this.ev().map(it) } - - fun handleErrorWith(f: (CharacterError) -> AsyncResult): AsyncResult = - AsyncResult(KME().handleErrorWith(value, f.andThen { it.ev().value })) - - fun run(d: D): EitherT = value.run(d).ev() - - companion object { - - fun ETM(): MonadError, CharacterError> = - EitherT.monadError() - - fun KME(): KleisliMonadErrorInstance, D, CharacterError> = - KleisliMonadErrorInstanceImplicits.instance(ETM()) - - fun KMR(): KleisliMonadReaderInstance, D> = - KleisliMonadReaderInstanceImplicits.instance(ETM()) - - fun tailRecM(a: A, f: (A) -> AsyncResultKind>): AsyncResult = - AsyncResult(KME().tailRecM(a, f.andThen { it.ev().value })) - - fun pure(a: A): AsyncResult = - AsyncResult(KME().pure(a)) - - fun ask(): AsyncResult = - AsyncResult(KMR().ask()) - - fun unit(): AsyncResult = pure(Unit) - - fun local( - f: (D) -> D, fa: AsyncResultKind): AsyncResult = - AsyncResult(KMR().local(f, fa.ev().value)) - } -} - -interface AsyncResultFunctorInstance : Functor> { - - override fun map(fa: AsyncResultKind, f: (A) -> B): AsyncResult = - fa.ev().map(f) - -} - -object AsyncResultFunctorInstanceImplicits { - @JvmStatic - fun instance(): AsyncResultFunctorInstance = - object : AsyncResultFunctorInstance {} -} - -fun AsyncResult.Companion.functor(): AsyncResultFunctorInstance = - AsyncResultFunctorInstanceImplicits.instance() - -interface AsyncResultApplicativeInstance : AsyncResultFunctorInstance, Applicative> { - - override fun map(fa: AsyncResultKind, f: (A) -> B): AsyncResult = - fa.ev().map(f) - - override fun ap(fa: AsyncResultKind, ff: HK, (A) -> B>): AsyncResult = - fa.ev().ap(ff) - - override fun pure(a: A): AsyncResult = - AsyncResult.pure(a) - -} - -object AsyncResultApplicativeInstanceImplicits { - @JvmStatic - fun instance(): AsyncResultApplicativeInstance = - object : AsyncResultApplicativeInstance {} -} - -fun AsyncResult.Companion.applicative(): AsyncResultApplicativeInstance = - AsyncResultApplicativeInstanceImplicits.instance() - -interface AsyncResultMonadInstance : AsyncResultApplicativeInstance, Monad> { - - override fun flatMap(fa: AsyncResultKind, f: (A) -> AsyncResultKind): AsyncResultKind = - fa.ev().flatMap(f) - - override fun ap(fa: AsyncResultKind, ff: HK, (A) -> B>): AsyncResult = - fa.ev().ap(ff) - - override fun tailRecM(a: A, f: (A) -> AsyncResultKind>): AsyncResultKind = - AsyncResult.tailRecM(a, f) -} - -object AsyncResultMonadInstanceImplicits { - @JvmStatic - fun instance(): AsyncResultMonadInstance = - object : AsyncResultMonadInstance {} -} - -fun AsyncResult.Companion.monad(): AsyncResultMonadInstance = - AsyncResultMonadInstanceImplicits.instance() - -interface AsyncResultMonadErrorInstance : AsyncResultMonadInstance, MonadError, CharacterError> { - - override fun raiseError(e: CharacterError): AsyncResult = - AsyncResult(AsyncResult.KME().raiseError(e)) - - override fun handleErrorWith(fa: AsyncResultKind, - f: (CharacterError) -> AsyncResultKind): AsyncResult = - fa.ev().handleErrorWith(f.andThen { it.ev() }) - -} - -object AsyncResultMonadErrorInstanceImplicits { - @JvmStatic - fun instance(): AsyncResultMonadErrorInstance = - object : AsyncResultMonadErrorInstance {} -} - -fun AsyncResult.Companion.monadError(): AsyncResultMonadErrorInstance = - AsyncResultMonadErrorInstanceImplicits.instance() - -interface AsyncResultMonadReaderInstance : AsyncResultMonadErrorInstance, MonadReader, D> { - - override fun ask(): HK, D> = AsyncResult(KMR().ask()) - - override fun local(f: (D) -> D, fa: HK, A>): HK, A> = - AsyncResult(KMR().local(f, fa.ev().value)) -} - -object AsyncResultMonadReaderInstanceImplicits { - @JvmStatic - fun instance(): AsyncResultMonadReaderInstance = - object : AsyncResultMonadReaderInstance {} -} - -fun AsyncResult.Companion.monadReader(): AsyncResultMonadReaderInstance = - AsyncResultMonadReaderInstanceImplicits.instance() diff --git a/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/functional/Future.kt b/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/functional/Future.kt deleted file mode 100644 index 6eeda50..0000000 --- a/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/functional/Future.kt +++ /dev/null @@ -1,53 +0,0 @@ -package com.github.jorgecastillo.kotlinandroid.functional - -import kategory.* -import kotlinx.coroutines.experimental.CommonPool -import kotlinx.coroutines.experimental.Deferred -import kotlinx.coroutines.experimental.android.UI -import kotlinx.coroutines.experimental.async -import kotlinx.coroutines.experimental.launch - -/** - * Basic future implementation to achieve asynchronicity based on Kotlin coroutines. - */ -@higherkind -@deriving(Functor::class, Applicative::class, Monad::class) -class Future : FutureKind { - - private val deferred: Deferred - - private constructor(deferred: Deferred) { - this.deferred = deferred - } - - constructor(f: () -> T) : this(async(CommonPool) { f() }) - - fun map(f: (T) -> X): Future = Future(async(CommonPool) { f(deferred.await()) }) - - fun flatMap(f: (T) -> FutureKind): Future = - Future(async(CommonPool) { f(deferred.await()).ev().deferred.await() }) - - fun ap(ff: FutureKind<(T) -> B>): Future = - zip(ff).map { it.b(it.a) } - - fun zip(fb: FutureKind): Future> = - flatMap { a -> fb.ev().map { b -> Tuple2(a, b) } } - - fun onComplete(f: (T) -> Unit) { - launch(UI) { - f(deferred.await()) - } - } - - companion object { - - fun tailRecM(a: A, f: (A) -> FutureKind>): Future = - f(a).ev().flatMap { - it.fold({ tailRecM(a, f).ev() }, { Future.pure(it) }) - } - - fun pure(a: A): Future = - Future({ a }) - - } -} diff --git a/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/presentation/SuperHeroesPresentation.kt b/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/presentation/SuperHeroesPresentation.kt index a1ad858..78b5d7c 100644 --- a/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/presentation/SuperHeroesPresentation.kt +++ b/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/presentation/SuperHeroesPresentation.kt @@ -3,10 +3,11 @@ package com.github.jorgecastillo.kotlinandroid.presentation import com.github.jorgecastillo.kotlinandroid.domain.model.CharacterError import com.github.jorgecastillo.kotlinandroid.free.algebra.* import com.github.jorgecastillo.kotlinandroid.view.viewmodel.SuperHeroViewModel +import com.karumi.marvelapiclient.MarvelApiException +import com.karumi.marvelapiclient.MarvelAuthApiException import com.karumi.marvelapiclient.model.CharacterDto -import kategory.Either -import kategory.binding -import kategory.ev +import kategory.* +import java.net.HttpURLConnection interface SuperHeroesView { fun showNotFoundError() @@ -24,15 +25,24 @@ interface SuperHeroDetailView : SuperHeroesView { fun showSuperHeroes(): FreeHeroesAlgebra = HeroesAlgebra.binding { - val res: Either> = attempt(getAllHeroes()).bind() - val efRes = handlePresentationEffects(res).bind() + val res: Either> = attempt(getAllHeroes()).bind() + val efRes = handlePresentationEffects(res.bimap(::exceptionAsCharacterError, ::identity)).bind() yields(efRes) }.ev() fun showSuperHeroDetail(heroId: String): FreeHeroesAlgebra = HeroesAlgebra.binding { - val res: Either = attempt(getSingleHero(heroId)).bind() - val efRes = handlePresentationEffects(res.map(::listOf)).bind() + val res: Either = attempt(getSingleHero(heroId)).bind() + val efRes = handlePresentationEffects(res.bimap(::exceptionAsCharacterError, ::listOf)).bind() yields(efRes) }.ev() + +fun exceptionAsCharacterError(e: Throwable): CharacterError = + when (e) { + is MarvelAuthApiException -> CharacterError.AuthenticationError + is MarvelApiException -> + if (e.httpCode == HttpURLConnection.HTTP_NOT_FOUND) CharacterError.NotFoundError + else CharacterError.UnknownServerError(Option.Some(e)) + else -> CharacterError.UnknownServerError((Option.Some(e))) + } diff --git a/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/view/SuperHeroListActivity.kt b/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/view/SuperHeroListActivity.kt index eb159ab..1b3d065 100644 --- a/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/view/SuperHeroListActivity.kt +++ b/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/view/SuperHeroListActivity.kt @@ -9,13 +9,11 @@ import com.github.jorgecastillo.kotlinandroid.di.context.SuperHeroesContext import com.github.jorgecastillo.kotlinandroid.di.context.SuperHeroesContext.GetHeroesContext import com.github.jorgecastillo.kotlinandroid.free.algebra.FreeHeroesAlgebra import com.github.jorgecastillo.kotlinandroid.free.interpreter.interpreter -import com.github.jorgecastillo.kotlinandroid.functional.AsyncResult -import com.github.jorgecastillo.kotlinandroid.functional.ev -import com.github.jorgecastillo.kotlinandroid.functional.monadError import com.github.jorgecastillo.kotlinandroid.presentation.SuperHeroesListView import com.github.jorgecastillo.kotlinandroid.presentation.showSuperHeroes import com.github.jorgecastillo.kotlinandroid.view.adapter.HeroesCardAdapter import com.github.jorgecastillo.kotlinandroid.view.viewmodel.SuperHeroViewModel +import kategory.effects.* import kategory.foldMap import kotlinx.android.synthetic.main.activity_main.* @@ -62,7 +60,8 @@ class SuperHeroListActivity : AppCompatActivity(), SuperHeroesListView { } -inline fun FreeHeroesAlgebra.unsafePerformEffects(ctx: SuperHeroesContext): AsyncResult { - val ME = AsyncResult.monadError() - return this.foldMap(interpreter(ctx, ME), ME).ev() +fun FreeHeroesAlgebra.unsafePerformEffects(ctx: SuperHeroesContext): Unit { + val ME = IO.monadError() + val result: IO = this.foldMap(interpreter(ctx, ME, IO.asyncContext()), ME).ev() + result.unsafeRunAsync { TODO() } } \ No newline at end of file From 03afd25cff45d1808b2ff93819a06792e0377595 Mon Sep 17 00:00:00 2001 From: Jorge Castillo Date: Sat, 7 Oct 2017 08:02:45 +0200 Subject: [PATCH 16/19] reformatting interpreter --- .../free/interpreter/Interpreter.kt | 174 ++++++++++-------- 1 file changed, 94 insertions(+), 80 deletions(-) diff --git a/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/free/interpreter/Interpreter.kt b/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/free/interpreter/Interpreter.kt index f9b1f5e..1b2b70e 100644 --- a/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/free/interpreter/Interpreter.kt +++ b/free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/free/interpreter/Interpreter.kt @@ -4,110 +4,124 @@ import com.github.jorgecastillo.kotlinandroid.di.context.SuperHeroesContext import com.github.jorgecastillo.kotlinandroid.di.context.SuperHeroesContext.GetHeroDetailsContext import com.github.jorgecastillo.kotlinandroid.di.context.SuperHeroesContext.GetHeroesContext import com.github.jorgecastillo.kotlinandroid.domain.model.CharacterError -import com.github.jorgecastillo.kotlinandroid.domain.model.CharacterError.* -import com.github.jorgecastillo.kotlinandroid.free.algebra.* +import com.github.jorgecastillo.kotlinandroid.domain.model.CharacterError.AuthenticationError +import com.github.jorgecastillo.kotlinandroid.domain.model.CharacterError.NotFoundError +import com.github.jorgecastillo.kotlinandroid.domain.model.CharacterError.UnknownServerError +import com.github.jorgecastillo.kotlinandroid.free.algebra.HeroesAlgebra +import com.github.jorgecastillo.kotlinandroid.free.algebra.HeroesAlgebraHK +import com.github.jorgecastillo.kotlinandroid.free.algebra.HeroesAlgebraKind +import com.github.jorgecastillo.kotlinandroid.free.algebra.ev import com.github.jorgecastillo.kotlinandroid.view.viewmodel.SuperHeroViewModel import com.karumi.marvelapiclient.model.CharacterDto import com.karumi.marvelapiclient.model.CharactersQuery.Builder import com.karumi.marvelapiclient.model.MarvelImage -import kategory.* +import kategory.Either import kategory.Either.Left import kategory.Either.Right +import kategory.FunctionK +import kategory.HK +import kategory.MonadError +import kategory.Try +import kategory.bindingE +import kategory.catch import kategory.effects.AsyncContext +import kategory.foldMap +import kategory.right import kotlinx.coroutines.experimental.CommonPool import kotlinx.coroutines.experimental.async -import kotlinx.coroutines.experimental.runBlocking @Suppress("UNCHECKED_CAST") -fun interpreter(ctx: SuperHeroesContext, ME: MonadError, AC: AsyncContext): FunctionK = - object : FunctionK { - override fun invoke(fa: HeroesAlgebraKind): HK { - val op = fa.ev() - return when (op) { - is HeroesAlgebra.GetAll -> getAllHeroesImpl(ctx, ME, AC) - is HeroesAlgebra.GetSingle -> getHeroDetailsImpl(ctx, ME, AC, op.heroId) - is HeroesAlgebra.HandlePresentationEffects -> { - ME.catch({ handlePresentationEffectsImpl(ctx, op.result) }) - } - is HeroesAlgebra.Attempt<*> -> { - val result = op.fa.foldMap(interpreter(ctx, ME, AC), ME) - ME.attempt(result) - } - } as HK - } - } +fun interpreter(ctx: SuperHeroesContext, ME: MonadError, + AC: AsyncContext): FunctionK = + object : FunctionK { + override fun invoke(fa: HeroesAlgebraKind): HK { + val op = fa.ev() + return when (op) { + is HeroesAlgebra.GetAll -> getAllHeroesImpl(ctx, ME, AC) + is HeroesAlgebra.GetSingle -> getHeroDetailsImpl(ctx, ME, AC, op.heroId) + is HeroesAlgebra.HandlePresentationEffects -> { + ME.catch({ handlePresentationEffectsImpl(ctx, op.result) }) + } + is HeroesAlgebra.Attempt<*> -> { + val result = op.fa.foldMap(interpreter(ctx, ME, AC), ME) + ME.attempt(result) + } + } as HK + } + } private fun runInAsyncContext( - f: () -> A, - onError: (Throwable) -> B, - onSuccess: (A) -> B, AC: AsyncContext): HK { - return AC.runAsync { proc -> - async(CommonPool) { - val result = Try { f() }.fold(onError, onSuccess) - proc(result.right()) - } + f: () -> A, + onError: (Throwable) -> B, + onSuccess: (A) -> B, + AC: AsyncContext): HK { + return AC.runAsync { proc -> + async(CommonPool) { + val result = Try { f() }.fold(onError, onSuccess) + proc(result.right()) } + } } fun getAllHeroesImpl( - ctx: SuperHeroesContext, - ME: MonadError, - AC: AsyncContext): HK> { - return ME.bindingE { - val query = Builder.create().withOffset(0).withLimit(50).build() - runInAsyncContext( - f = {ctx.apiClient.getAll(query).response.characters.toList()}, - onError = { ME.raiseError>(it) }, - onSuccess = { ME.pure(it) }, - AC = AC - ).bind() - } + ctx: SuperHeroesContext, + ME: MonadError, + AC: AsyncContext): HK> { + return ME.bindingE { + val query = Builder.create().withOffset(0).withLimit(50).build() + runInAsyncContext( + f = { ctx.apiClient.getAll(query).response.characters.toList() }, + onError = { ME.raiseError>(it) }, + onSuccess = { ME.pure(it) }, + AC = AC + ).bind() + } } fun getHeroDetailsImpl( - ctx: SuperHeroesContext, - ME: MonadError, - AC: AsyncContext, - heroId: String): HK = - ME.bindingE { - runInAsyncContext( - f = { ctx.apiClient.getCharacter(heroId).response }, - onError = { ME.raiseError(it) }, - onSuccess = { ME.pure(it) }, - AC = AC - ).bind() - } + ctx: SuperHeroesContext, + ME: MonadError, + AC: AsyncContext, + heroId: String): HK = + ME.bindingE { + runInAsyncContext( + f = { ctx.apiClient.getCharacter(heroId).response }, + onError = { ME.raiseError(it) }, + onSuccess = { ME.pure(it) }, + AC = AC + ).bind() + } fun handlePresentationEffectsImpl( - ctx: SuperHeroesContext, - result: Either>): Unit = - when (result) { - is Left -> { - displayErrors(ctx, result.a); } - is Right -> when (ctx) { - is GetHeroesContext -> ctx.view.drawHeroes(result.b.map { - SuperHeroViewModel( - it.id, - it.name, - it.thumbnail.getImageUrl(MarvelImage.Size.PORTRAIT_UNCANNY), - it.description) - }) - is GetHeroDetailsContext -> ctx.view.drawHero(result.b.map { - SuperHeroViewModel( - it.id, - it.name, - it.thumbnail.getImageUrl(MarvelImage.Size.PORTRAIT_UNCANNY), - it.description) - }[0]) - } - } + ctx: SuperHeroesContext, + result: Either>): Unit = + when (result) { + is Left -> { + displayErrors(ctx, result.a); } + is Right -> when (ctx) { + is GetHeroesContext -> ctx.view.drawHeroes(result.b.map { + SuperHeroViewModel( + it.id, + it.name, + it.thumbnail.getImageUrl(MarvelImage.Size.PORTRAIT_UNCANNY), + it.description) + }) + is GetHeroDetailsContext -> ctx.view.drawHero(result.b.map { + SuperHeroViewModel( + it.id, + it.name, + it.thumbnail.getImageUrl(MarvelImage.Size.PORTRAIT_UNCANNY), + it.description) + }[0]) + } + } fun displayErrors(ctx: SuperHeroesContext, c: CharacterError): Unit { - when (c) { - is NotFoundError -> ctx.view.showNotFoundError() - is UnknownServerError -> ctx.view.showGenericError() - is AuthenticationError -> ctx.view.showAuthenticationError() - } + when (c) { + is NotFoundError -> ctx.view.showNotFoundError() + is UnknownServerError -> ctx.view.showGenericError() + is AuthenticationError -> ctx.view.showAuthenticationError() + } } From 8f99f27bc64d11c5639d53f4b5682dc10f169126 Mon Sep 17 00:00:00 2001 From: Jorge Castillo Date: Sat, 7 Oct 2017 08:50:38 +0200 Subject: [PATCH 17/19] update readme with Free Monads section --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 065e852..1f480a7 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,9 @@ Higher Kinds to make our code depend on typeclass constrained behaviors, leaving which concrete types to use to the moment when we want to run the code. [You will really want to look at this PR to have a very good and detailed description of what tagless-final is](https://github.com/JorgeCastilloPrz/KotlinAndroidFunctional/pull/2). ## Free Monads -TBA. This is going to be another cool approach using FP, but still not ready. +This FP style is very trendy. We are applying it over Android thanks to Kategory here, on the `free-monads` project module. It's highly recommended to take a look at [this PR]() in order to understand the approach. +**Free Monads** is based on the idea of composing an **AST** (abstract syntax tree) of computations with type `Free` which will never depend on implementation details but on abstractions defined by an algebra, which is an algebraic data type (ADT). We are defining it through a `sealed` class on this sample. +Those ops can be combined as blocks to create more complex ones. Then, we need an **interpreter** which will be in charge to provide implementation details for the moment when the user decides to run the whole AST providing semantics to it and a `Monad` instance to resolve all the effects. The user has the power of chosing which interpreter to use and which monad instance he wants to solve the problem. That enables testing, since we can easily remove our real side effects in the app at our testing environment by switching the interpreter by a fake one. # Goals and rationales From 2400873a845c13496154c3b34ce50bb6014ab7f7 Mon Sep 17 00:00:00 2001 From: Jorge Castillo Date: Sat, 7 Oct 2017 08:51:19 +0200 Subject: [PATCH 18/19] update readme with PR reference --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1f480a7..618c083 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ Higher Kinds to make our code depend on typeclass constrained behaviors, leaving which concrete types to use to the moment when we want to run the code. [You will really want to look at this PR to have a very good and detailed description of what tagless-final is](https://github.com/JorgeCastilloPrz/KotlinAndroidFunctional/pull/2). ## Free Monads -This FP style is very trendy. We are applying it over Android thanks to Kategory here, on the `free-monads` project module. It's highly recommended to take a look at [this PR]() in order to understand the approach. +This FP style is very trendy. We are applying it over Android thanks to Kategory here, on the `free-monads` project module. It's highly recommended to take a look at [this PR](https://github.com/JorgeCastilloPrz/KotlinAndroidFunctional/pull/6) in order to understand the approach. **Free Monads** is based on the idea of composing an **AST** (abstract syntax tree) of computations with type `Free` which will never depend on implementation details but on abstractions defined by an algebra, which is an algebraic data type (ADT). We are defining it through a `sealed` class on this sample. Those ops can be combined as blocks to create more complex ones. Then, we need an **interpreter** which will be in charge to provide implementation details for the moment when the user decides to run the whole AST providing semantics to it and a `Monad` instance to resolve all the effects. The user has the power of chosing which interpreter to use and which monad instance he wants to solve the problem. That enables testing, since we can easily remove our real side effects in the app at our testing environment by switching the interpreter by a fake one. From d299449f009f3477ae4739bf614c148ec228d5a7 Mon Sep 17 00:00:00 2001 From: Jorge Castillo Date: Sat, 7 Oct 2017 14:57:42 +0200 Subject: [PATCH 19/19] fix readme suggestions --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 618c083..24d9a7b 100644 --- a/README.md +++ b/README.md @@ -52,10 +52,10 @@ which concrete types to use to the moment when we want to run the code. [You will really want to look at this PR to have a very good and detailed description of what tagless-final is](https://github.com/JorgeCastilloPrz/KotlinAndroidFunctional/pull/2). ## Free Monads This FP style is very trendy. We are applying it over Android thanks to Kategory here, on the `free-monads` project module. It's highly recommended to take a look at [this PR](https://github.com/JorgeCastilloPrz/KotlinAndroidFunctional/pull/6) in order to understand the approach. -**Free Monads** is based on the idea of composing an **AST** (abstract syntax tree) of computations with type `Free` which will never depend on implementation details but on abstractions defined by an algebra, which is an algebraic data type (ADT). We are defining it through a `sealed` class on this sample. -Those ops can be combined as blocks to create more complex ones. Then, we need an **interpreter** which will be in charge to provide implementation details for the moment when the user decides to run the whole AST providing semantics to it and a `Monad` instance to resolve all the effects. The user has the power of chosing which interpreter to use and which monad instance he wants to solve the problem. That enables testing, since we can easily remove our real side effects in the app at our testing environment by switching the interpreter by a fake one. +**Free Monads** is based on the idea of composing an **AST** (abstract syntax tree) of computations with type `Free`, where `S` is your algebra, which will never depend on implementation details but on abstractions defined by an algebra, which is an algebraic data type (ADT). We are defining it through a `sealed` class on this sample. +Those ops can be combined as blocks to create more complex ones. Then, we need an **interpreter** which will be in charge to provide implementation details for the moment when the user decides to run the whole AST providing semantics to it and a `Monad` instance to resolve all effects / perform execution of effects in a controlled context. The user has the power of chosing which interpreter to use and which monad instance he wants to solve the problem. That enables testing, since we can easily remove our real side effects in the app at our testing environment by switching the interpreter by a fake one. -# Goals and rationales +# Goals and rationale ## Modeling success and error cases **Referential transparency** from a function perspective means that it should be clearly defining