Share
Embed
[JP] Functional Programming in Kotlin with Arrow
[JP] Functional Programming in Kotlin with Arrow
Daichi Furiya (Wasabeef)
September 28, 2018
Transcript
59 slides
-
build.gradle def arrow_version = "0.7.3" !// 2018.9.28 dependencies { implementation
"io.arrow-kt:arrow-core:$arrow_version" implementation "io.arrow-kt:arrow-syntax:$arrow_version" implementation "io.arrow-kt:arrow-typeclasses:$arrow_version" implementation "io.arrow-kt:arrow-data:$arrow_version" implementation "io.arrow-kt:arrow-instances-core:$arrow_version" implementation "io.arrow-kt:arrow-instances-data:$arrow_version" kapt "io.arrow-kt:arrow-annotations-processor:$arrow_version" implementation "io.arrow-kt:arrow-free:$arrow_version" !// optional implementation "io.arrow-kt:arrow-mtl:$arrow_version" !// optional implementation "io.arrow-kt:arrow-effects:$arrow_version" !// optional implementation "io.arrow-kt:arrow-effects-rx2:$arrow_version" !// optional implementation "io.arrow-kt:arrow-effects-reactor:$arrow_version" !// optional implementation “io.arrow-kt:arrow-effects-kotlinx-coroutines:$arrow_version" !// optional implementation "io.arrow-kt:arrow-optics:$arrow_version" !// optional implementation "io.arrow-kt:arrow-generic:$arrow_version" !// optional implementation "io.arrow-kt:arrow-recursion:$arrow_version" !// optional } -
Data Types %BUB5ZQFT 0QUJPO &JUIFS 5SZ 7BMJEBUFE /PO&NQUZ-JTU -JTU, 4FRVFODF,
4FU, .BQ, 4PSUFE.BQ, *PS *E 3FBEFS ,MFJTJM 4UBUF 4UBUF5 8SJUF5 5SBNQPMJOF $PQSPEVDU &WBM &JUIFS5 'VODUJPO 'VODUJPO $POTU 5ZQF$MBTTFT 4IPX &R 0SEFS 4FNJHSPVQ .POPJE 'PMEBCMF #JGPMEBCMF 5SBWFSTF 3FEVDJCMF 5SBWFSTF'JMUFS 'VODUPS 'VODUPS'JMUFS "QQMJDBUJWF "QQMJDBUJWF&SSPS .POBE .POBE&SSPS .POBE'JMUFS .POBE3FBEFS .POBE8SJUFS .POBE4UBUF .POBE$PNCJOF $PNPOBE #JNPOBE #JGVODUPS 1SPGVODUPS 4FNJHSPVQ, .POBE, *OKFDU "MUFSOBUJWF &⒎FDUT *0 .POBE%FGFS "TZOD &⒎FDU 0QUJDT *TP -FOT 0QUJPOBM 1SJTN (FUUFS 4FUUFS 'PME 5SBWFSTBM "U *OEFY 'JMUFS*OEFY &BDI -
Data Types %BUB5ZQFT 0QUJPO &JUIFS 5SZ 7BMJEBUFE /PO&NQUZ-JTU -JTU, 4FRVFODF,
4FU, .BQ, 4PSUFE.BQ, *PS *E 3FBEFS ,MFJTJM 4UBUF 4UBUF5 8SJUF5 5SBNQPMJOF $PQSPEVDU &WBM &JUIFS5 'VODUJPO 'VODUJPO $POTU 5ZQF$MBTTFT 4IPX &R 0SEFS 4FNJHSPVQ .POPJE 'PMEBCMF #JGPMEBCMF 5SBWFSTF 3FEVDJCMF 5SBWFSTF'JMUFS 'VODUPS 'VODUPS'JMUFS "QQMJDBUJWF "QQMJDBUJWF&SSPS .POBE .POBE&SSPS .POBE'JMUFS .POBE3FBEFS .POBE8SJUFS .POBE4UBUF .POBE$PNCJOF $PNPOBE #JNPOBE #JGVODUPS 1SPGVODUPS 4FNJHSPVQ, .POBE, *OKFDU "MUFSOBUJWF &⒎FDUT *0 .POBE%FGFS "TZOD &⒎FDU 0QUJDT *TP -FOT 0QUJPOBM 1SJTN (FUUFS 4FUUFS 'PME 5SBWFSTBM "U *OEFY 'JMUFS*OEFY &BDI -
Option<A> sealed class Option<out A> : OptionOf<A> { !!... }
object None : Option<Nothing>() { !!... } data class Some<out T>(val t: T) : Option<T>() { !!... } Option 周りで頻繁に登場するクラス群 -
Option<A> val opt1 = Option("Hello").map { it + "World" }
!// Option("HelloWorld") val opt2 = Option.fromNullable(null) !// None !// inline fun <B> map(f: (A) !-> B): Option<B> = !// flatMap { a !-> Some(f(a)) } シンプルな構文例 if文などを使わずとも Option#map, flatMap もある -
Option<A> 値がないことをより安全に扱うため val aruu: String = "HelloWorld" !// ͋Γ var
naii: String? = null !// ͳ͠ val some = Some("HelloWorld") !// ͋Γ val none = None !// ͳ͠ -
Option<A> when での判定する場合は val bitmap = Option.fromNullable(BitmapFactory.decodeStream(!!...)) when (bitmap) {
is None !-> { !!... } is Some !-> { !!... } } -
Option<A> Option#fold 使うことで None/Some の when を簡素化も val bitmap =
Option.fromNullable(BitmapFactory.decodeStream(!!...)) bitmap.fold({ !// if None },{ !// if Some }) -
Option<A> Option#flatMap, filter, toList…etc なども使えます val numm = Option("123").flatMap {
try { Some(it.toInt()) } catch (e: NumberFormatException) { None } } !// Some(t=123) -
Either<A, B> Either 周りで頻繁に登場するクラス群 sealed class Either<out A, out B>
: EitherOf<A, B> { !!... } data class Left<out A> ɹinternal constructor(val a: A) : Either<A, Nothing>() { !!... } data class Right<out B> ɹinternal constructor(val b: B) : Either<Nothing, B>() { !!... } -
Either<A, B> シンプルな構文例 val eiei = Right("123").map { it.toInt() }
!// Right(b=123) val eiei2 = Left(IllegalArgumentException("OMG!")) !// Left(a=IllegalArgumentException) -
Either<A, B> try/catchを使ったシンプルな構文例 val eiei: Either<Throwable, Bitmap> = try {
Right(BitmapFactory.decodeStream(null)) } catch (e: IllegalArgumentException) { Left(e) } catch (e: OutOfMemoryError) { Left(e) } -
Either<A, B> If や fold での判定する場合は if (eiei.isLeft) { !!...
} if (eiei.isRight) { !!... } eiei.fold({ e !-> !// if Left }, { !// if Right }) -
Validated<E, A> Validated 周りで頻繁に登場するクラス群 sealed class Validated<out E, out A>
: ValidatedOf<A, B> { !!... } data class Valid<out A>(val a: A) : Validated<Nothing, A>() data class Invalid<out E>(val e: E) : Validated<E, Nothing>() -
Validated<E, A> シンプルな構文例 val aaa: Validated<Throwable, Int> = Valid(123) val
bbb: Validated<Throwable, Int> = Invalid(Throwable("eee")) -
Validated<E, A> If や fold での判定する場合は if (vali.isInvalid) { !!...
} if (vali.isValid) { !!... } vali.fold({ !// if Invalid }, { !// if Valid }) -
Validated<E, A> Validated#toOption で Option 型として扱う val aaa = Valid(123).toOption()
!// Some(t=123) val bbb = Invalid(Throwable()).toOption() !// None -
Validated<E, A> val aaa = Valid(123).toEither() !// Right(b=123) val bbb
= Invalid(Throwable()).toEither() !// Left(a=Throwable) Validated#toEither で Either 型として扱う -
Validated<E, A> val aaa = Invalid("abcd").orElse { Valid(123) } val
bbb = Invalid("abcd").getOrElse { 0 } Validated#orElse, getOrElse で Invalid の場合などに違う値を -
Semigroup<A> Semigroup 周りで頻繁に登場するクラス群 interface Semigroup<A> { fun A.combine(b: A): A
operator fun A.plus(b: A): A = this.combine(b) fun A.maybeCombine(b: A?): A = Option.fromNullable(b).fold({ this }, { combine(it) }) } -
Semigroup<A> Semigroup 使ってエラーを合成することができる val stringSemi: Semigroup<String> = object : Semigroup<String>
{ override fun String.combine(b: String): String = "$this + $b" } val vavava = Invalid("Hello").findValid(stringSemi) { Invalid("World") } !// vavava.toEither() !=> Left(a=HelloWorld) !// vavava.toOption() !=> None -
Try<A> Try 周りで頻繁に登場するクラス群 sealed class Try<out A> : TryOf<A> {
!!... } data class Failure(val exception: Throwable) : Try<Nothing>() { data class Success<out A>(val value: A) : Try<A>() { -
Try<A> シンプルな構文例 val aaa: Try<Int> = Try { 123 }
val bbb: Try<Int> = Try { throw Throwable("OMG!") } -
Try<A> If や fold での判定する場合は if (trrr.isFailure) { !!... }
if (trrr.isSuccess) { !!... } trrr.fold({ e !-> !// if Failure }, { !// if Success }) -
Try<A> Try#toOption で Option 型として扱う val aaa: Try<Int> = Try
{ 123 } aaa.toOption() !// Some(t=123) val bbb: Try<Int> = Try { throw Throwable("OMG!") } bbb.toOption() !// None -
Try<A> Try#toEither で Either 型として扱う val aaa: Try<Int> = Try
{ 123 } aaa.toEither() !// Right(b=123) val bbb: Try<Int> = Try { throw Throwable("OMG!") } bbb.toEither() !// Left(a=Throwable) -
Try<A> Try#recover で代替の値を設定して Either 型として扱う val aaa: Try<Int> = Try
{ throw Throwable("OMG!") } val bbb = aaa.recover { e !-> 123 }.toEither() -
Try<A> val aaa: Try<Int> = Try { throw Throwable("OMG!") }
val bbb = aaa.recoverWith { e !-> Try { throw OtherException("Oh!!...") } }.recover { e !-> 123 } Try#recoverWith で改めて別の処理を行い、それでも失敗する場合は recover で代替の値を設定して Either 型として扱うなど -
Option nullの代わりでより安全にする Either L, Rの値を持つことができ、エラー発生 時に値を持たせられる。ただしエラー値は1つだけ Validated Semigroup でエラーを集約できる Try
Throwable を Option や Either に変換する ここまでのまとめ -
kotlinx.coroutines integration Deferred 周りで頻繁に登場する拡張関数、及びクラス fun <A> Deferred<A>.k(): DeferredK<A> = DeferredK(this)
data class DeferredK<out A>(val deferred: Deferred<A>) : DeferredKOf<A>, Deferred<A> by deferred { … } -
kotlinx.coroutines integration val deferred = async { throw RuntimeException("OMG!") }
runBlocking { deferred.await() } !// CRASH! Exception! val wrapper = async { throw RuntimeException("OMG!") }.k() val eiei = wrapper.unsafeAttemptSync().toEither() !// Left(a=RuntimeException) Deferred#k() で Deferred をラップすることで Arrow の世界に持ち込む -
kotlinx.coroutines integration DeferredK { BitmapFactory.decodeFile(!!...) } .runAsync { result !->
result.fold({ DeferredK { /** if Throwable **/ } }, { bitmap !-> DeferredK { /** Use the Bitmap **/ } }) } DeferredK は Deferred のArrowラッパーで runAsync は Either を返却してくれる