New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Free Monads Sample #6
Merged
Merged
Changes from 18 commits
Commits
Show all changes
19 commits
Select commit
Hold shift + click to select a range
48b5da8
add free monads module as a copy of tagless final one
JorgeCastilloPrz 5af3c98
remove some unused classes
JorgeCastilloPrz b5cdc71
add algebra and interpreter to data layer
JorgeCastilloPrz bfcff99
fix problems
JorgeCastilloPrz d398850
optimize imports
JorgeCastilloPrz 9b22ce5
rename algebra to be shared
JorgeCastilloPrz db9d517
fix repository layer
JorgeCastilloPrz 2fed37b
fix use cases
JorgeCastilloPrz 9804325
rename interpreter
JorgeCastilloPrz e7954f9
move free related classes to more vertical package
JorgeCastilloPrz 84362f7
add presentation effects methods to algebra
JorgeCastilloPrz 97f1417
refactor to HandlePresentationEffects as an algebra op
JorgeCastilloPrz 2462313
refactor and add some presentation effects
JorgeCastilloPrz 1ce811d
Free monads refactoring
raulraja 9f43b4f
Interpret to IO
raulraja 03afd25
reformatting interpreter
JorgeCastilloPrz 8f99f27
update readme with Free Monads section
JorgeCastilloPrz 2400873
update readme with PR reference
JorgeCastilloPrz d299449
fix readme suggestions
JorgeCastilloPrz File filter
Filter by extension
Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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](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<T>` 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. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
||
# Goals and rationales | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
/build |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
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 "io.kategory:kategory-effects:$kategory_version" | ||
compile 'com.karumi:marvelapiclient:1.0.1' | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
24 changes: 24 additions & 0 deletions
24
.../src/androidTest/java/com/github/jorgecastillo/kotlinandroid/ExampleInstrumentedTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 <a href="http://d.android.com/tools/testing">Testing documentation</a> | ||
*/ | ||
@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()); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
<?xml version="1.0" encoding="utf-8"?> | ||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" | ||
package="com.github.jorgecastillo.kotlinandroid"> | ||
|
||
<uses-permission android:name="android.permission.INTERNET"/> | ||
|
||
<application | ||
android:allowBackup="true" | ||
android:icon="@mipmap/ic_launcher" | ||
android:label="@string/app_name" | ||
android:supportsRtl="true" | ||
android:theme="@style/AppTheme"> | ||
<activity android:name=".view.SuperHeroListActivity"> | ||
<intent-filter> | ||
<action android:name="android.intent.action.MAIN"/> | ||
|
||
<category android:name="android.intent.category.LAUNCHER"/> | ||
</intent-filter> | ||
</activity> | ||
|
||
<activity | ||
android:name=".view.SuperHeroDetailActivity" | ||
android:theme="@style/NoActionBar"/> | ||
</application> | ||
</manifest> |
26 changes: 26 additions & 0 deletions
26
free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/context/GetHeroesContext.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
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.karumi.marvelapiclient.CharacterApiClient | ||
import com.karumi.marvelapiclient.MarvelApiConfig.Builder | ||
|
||
sealed class SuperHeroesContext { | ||
|
||
abstract val ctx: Context | ||
abstract val view: SuperHeroesView | ||
|
||
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() | ||
} | ||
|
||
|
35 changes: 35 additions & 0 deletions
35
free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/data/HeroesRepository.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
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.free.algebra.FreeHeroesAlgebra | ||
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 { | ||
object NetworkOnly : CachePolicy() | ||
object NetworkFirst : CachePolicy() | ||
object LocalOnly : CachePolicy() | ||
object LocalFirst : CachePolicy() | ||
} | ||
|
||
fun getHeroesWithCachePolicy(policy: CachePolicy): FreeHeroesAlgebra<List<CharacterDto>> = | ||
when (policy) { | ||
is NetworkOnly -> getAllHeroes() | ||
is NetworkFirst -> getAllHeroes() | ||
is LocalOnly -> getAllHeroes() | ||
is LocalFirst -> getAllHeroes() | ||
} | ||
|
||
fun getHeroDetails(policy: CachePolicy, heroId: String): FreeHeroesAlgebra<CharacterDto> = | ||
when (policy) { | ||
is NetworkOnly -> getSingleHero(heroId) | ||
is NetworkFirst -> getSingleHero(heroId) | ||
is LocalOnly -> getSingleHero(heroId) | ||
is LocalFirst -> getSingleHero(heroId) | ||
} | ||
|
||
fun getHeroesFromAvengerComicsWithCachePolicy(policy: CachePolicy): FreeHeroesAlgebra<List<CharacterDto>> = TODO() |
21 changes: 21 additions & 0 deletions
21
...onads/src/main/java/com/github/jorgecastillo/kotlinandroid/domain/model/CharacterError.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<Throwable> = Option.None) : CharacterError() | ||
} |
3 changes: 3 additions & 0 deletions
3
free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/domain/model/SuperHero.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
package com.github.jorgecastillo.kotlinandroid.domain.model | ||
|
||
data class SuperHero(val name: String) |
17 changes: 17 additions & 0 deletions
17
...ads/src/main/java/com/github/jorgecastillo/kotlinandroid/domain/usecase/HeroesUseCases.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
package com.github.jorgecastillo.kotlinandroid.domain.usecase | ||
|
||
import com.github.jorgecastillo.kotlinandroid.data.CachePolicy.NetworkOnly | ||
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 | ||
import com.karumi.marvelapiclient.model.CharacterDto | ||
|
||
fun getHeroesUseCase(): FreeHeroesAlgebra<List<CharacterDto>> = | ||
getHeroesWithCachePolicy(NetworkOnly) | ||
|
||
fun getHeroDetailsUseCase(heroId: String): FreeHeroesAlgebra<CharacterDto> = | ||
getHeroDetails(NetworkOnly, heroId) | ||
|
||
fun getHeroesFromAvengerComicsUseCase(): FreeHeroesAlgebra<List<CharacterDto>> = | ||
getHeroesFromAvengerComicsWithCachePolicy(NetworkOnly) |
38 changes: 38 additions & 0 deletions
38
free-monads/src/main/java/com/github/jorgecastillo/kotlinandroid/free/algebra/algebras.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
package com.github.jorgecastillo.kotlinandroid.free.algebra | ||
|
||
import com.github.jorgecastillo.kotlinandroid.domain.model.CharacterError | ||
import com.karumi.marvelapiclient.model.CharacterDto | ||
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<A> : HeroesAlgebraKind<A> { | ||
object GetAll : HeroesAlgebra<List<CharacterDto>>() | ||
class GetSingle(val heroId: String) : HeroesAlgebra<CharacterDto>() | ||
class HandlePresentationEffects(val result: Either<CharacterError, List<CharacterDto>>) : HeroesAlgebra<Unit>() | ||
class Attempt<A>(val fa: FreeHeroesAlgebra<A>): HeroesAlgebra<Either<Throwable, A>>() | ||
companion object : FreeMonadInstance<HeroesAlgebraHK> | ||
} | ||
|
||
typealias FreeHeroesAlgebra<A> = Free<HeroesAlgebraHK, A> | ||
|
||
inline fun <reified F> Free<HeroesAlgebraHK, List<CharacterDto>>.run( | ||
interpreter: FunctionK<HeroesAlgebraHK, F>, MF: Monad<F> = monad()): HK<F, List<CharacterDto>> = | ||
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<List<CharacterDto>> = | ||
Free.liftF(HeroesAlgebra.GetAll) | ||
|
||
fun getSingleHero(heroId: String): FreeHeroesAlgebra<CharacterDto> = | ||
Free.liftF(HeroesAlgebra.GetSingle(heroId)) | ||
|
||
fun handlePresentationEffects(result: Either<CharacterError, List<CharacterDto>>): FreeHeroesAlgebra<Unit> = | ||
Free.liftF(HeroesAlgebra.HandlePresentationEffects(result)) | ||
|
||
fun <A> attempt(fa: FreeHeroesAlgebra<A>): FreeHeroesAlgebra<Either<Throwable, A>> = | ||
Free.liftF(HeroesAlgebra.Attempt(fa)) | ||
|
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Free<S, A>
whereS
is your algebra