Companion Object
Total Page:16
File Type:pdf, Size:1020Kb
1. Functional Programming Functional Programming What is functional programming? What is it good for? Why to learn it? Functional Programming Programs as a composition of pure functions. Pure function? Deterministic. Same result for the same inputs. Produces no observable side eects. fun mergeAccounts(acc1: Account, acc2: Account): Account = Account(acc1.balance + acc2.balance) val mergedAcc = mergeAccounts(Account(500), Account(1000)) Target platform: JVM Running on Arrow v. 0.11.0-SNAPSHOT Running on Kotlin v. 1.3.50 Referential Transparency Function can be replaced with its corresponding value without changing the program's behavior. Based on the substitution model. Referential Transparency Better reasoning about program behavior. Pure functions referentially transparent. Substitution model Replace a named reference (function) by its value. Based on equational reasoning. Unlocks local reasoning. fun add(a: Int, b: Int): Int = a + b val two = add(1, 1) // could be replaced by 2 everywhere in the program val equal = (two == 1 + 1) Target platform: JVM Running on Arrow v. 0.11.0-SNAPSHOT Running on Kotlin v. 1.3.50 Substitution model Same example, but with two Accounts. fun mergeAccounts(acc1: Account, acc2: Account): Account = Account(acc1.balance + acc2.balance) val mergedAcc = mergeAccounts(Account(100), Account(200)) // could be replaced by Account(300) everywhere // in the program val equal = (mergedAcc == Account(300)) Target platform: JVM Running on Arrow v. 0.11.0-SNAPSHOT Running on Kotlin v. 1.3.50 Side eect? Function tries to access or modify the external world. val accountDB = AccountDB() fun mergeAccounts(acc1: Account, acc2: Account): Account { val merged = Account(acc1.balance + acc2.balance) accountDB.remove(listOf(acc1, acc2)) // side effect! accountDB.save(merged) // side effect! return merged } val merged = mergeAccounts(Account(100), Account(400)) Target platform: JVM Running on Arrow v. 0.11.0-SNAPSHOT Running on Kotlin v. 1.3.50 Side eect? They are not referentially transparent so we can't apply equational reasoning by substitution. Higher order functions Functions that have other functions as arguments or return other functions. Unlocks storing and deferring computations. val acc = Account(1000) fun Account.updateBalance(op: (Long) -> Long): Account { return Account(op(this.balance)) } val millionaireAcc = acc.updateBalance { it + 4000000 } Target platform: JVM Running on Arrow v. 0.11.0-SNAPSHOT Running on Kotlin v. 1.3.50 Higher order functions val acc = Account(1000) fun Account.updateBalance(op: (Long) -> Long): () -> Account = Account(op(this.balance)) } val millionaireAcc = acc.updateBalance { it + 4000000 } Target platform: JVM Running on Arrow v. 0.11.0-SNAPSHOT Running on Kotlin v. 1.3.50 Reason about our functions as if they were data. Exercises 2. Domain modeling and Algebraic Data Types What's an ADT? Composite type (A type that combines others). Product types Sum types Product types Example: Kotlin data classes. Composed by other types. All of them have to exit. A & B & C & D A * B * C * D Structurally dened by its types. Product types - Data classes // product types data class User(val userId: String, val firstname: String, val data class Account(val id: String, val owner: User, val balance data class Debit(val acc: Account, val amount: Long) val acc = Account("acc01928j", User("SomeId", "Jorge", "Castill val debitOp = Debit(acc, 500) Target platform: JVM Running on Arrow v. 0.11.0-SNAPSHOT Running on Kotlin v. 1.3.50 Product types - Data classes equals and hashcode derived at compile time based on their structure. // product types data class User(val userId: String, val firstname: String, val data class Account(val id: String, val owner: User, val balance data class Debit(val acc: Account, val amount: Long) val acc1 = Account("acc01928j", User("SomeId", "Jorge", "Castil val acc2 = Account("acc01928j", User("SomeId", "Jorge", "Castil val result = acc1 == acc2 Target platform: JVM Running on Arrow v. 0.11.0-SNAPSHOT Running on Kotlin v. 1.3.50 Product types - Data classes Synthetic copy method also derived at compile time based on their structure. // product types data class User(val userId: String, val firstname: String, val data class Account(val id: String, val owner: User, val balance data class Debit(val acc: Account, val amount: Long) val account = Account("acc01928j", User("SomeId", "Jorge", "Cas val result = account.copy(balance = account.balance + 1000) Target platform: JVM Running on Arrow v. 0.11.0-SNAPSHOT Running on Kotlin v. 1.3.50 Arrow Product Types @product annotation over a data class. It must include a companion object. Derives additional syntax at compile time. import arrow.core.* import arrow.generic.* @product data class Account(val balance: Int, val available: Int) { comp Arrow derives the following behaviors with @product: Monoid import arrow.core.* import arrow.generic.* val result = Account(1000, 900) + Account(1000, 900) // Account(2000, 1800) Arrow automatically derives with @product the following behaviors: Monoid import arrow.core.* import arrow.generic.* val result = emptyAccount() // Account(0, 0) relies on Int.monoid() Arrow automatically derives with @product the following behaviors: Arity abstraction import arrow.core.* import arrow.generic.* val result = Account(1000, 900).toHList() // HCons(head=1000, tail=HCons(head=900, tail=HNil)) Arrow automatically derives with @product the following behaviors: Arity abstraction import arrow.core.* import arrow.generic.* val result = Account(1000, 900).toHListLabeled() // HCons(head=(balance, 1000), tail=HCons(head=(available, 900) Arrow automatically derives with @product the following behaviors: Arity abstraction import arrow.core.* import arrow.generic.* val result = Account(1000, 900).tupled() // (1000, 900) Sum types Example: Sealed class, Enum class, Coproduct types, Union Types. One of many. Exclusive OR. A or B or C or D A + B + C + D Sum types - Sealed classes Algebra with multiple representations. enum class, sealed class, Boolean, Either... // Sum type, 2 representations sealed class Maybe<out A> { data class Present<out A>(val a: A) : Maybe<A>() object Absent : Maybe<Nothing>() } val result = Maybe.Present<Account>(Account(1000, 900)) Target platform: JVM Running on Arrow v. 0.11.0-SNAPSHOT Running on Kotlin v. 1.3.50 Sum types - Sealed classes // Sum type, 2 representations sealed class Xor<out A, out B> { data class Left<out A>(val a: A) : Xor<A, Nothing>() data class Right<out B>(val b: B) : Xor<Nothing, B>() } val result: Xor<Throwable, Account> = Xor.Right(Account(1000, 9 Target platform: JVM Running on Arrow v. 0.11.0-SNAPSHOT Running on Kotlin v. 1.3.50 Sum types - Sealed classes sealed class RecList<out A> { data class Cons<out A>(val a: A, val tail: RecList<A>) : Re object Nil: RecList<Nothing>() } val result: RecList<Account> = RecList.Cons( Account(1000, 900), RecList.Cons( Account(1000, 900), RecList.Nil ) ) Target platform: JVM Running on Arrow v. 0.11.0-SNAPSHOT Running on Kotlin v. 1.3.50 Sum types - Sealed classes // An expression language sealed class Term<out A> { data class Lit(val n: Int) : Term<Int>() data class Succ(val i: Term<Int>) : Term<Int>() data class IsZero(val i: Term<Int>) : Term<Boolean>() data class Pair<out A, out B>(val a: Term<A>, val b: Term<B } fun <A> eval(term: Term<A>): A = when (term) { is Term.Lit -> term.n is Term.Succ -> 1 + eval(term.i) is Term.IsZero -> eval(term.i) == 0 Target platform: JVM Running on Arrow v. 0.11.0-SNAPSHOT Running on Kotlin v. 1.3.50 Sum types - Coproduct Also a sealed hierarchy of types. Arbitrary amount of representations up to 22 args. import arrow.* import arrow.core.* sealed class Coproduct3<A, B, C> data class First<A, B, C>(val a: A) : Coproduct3<A, B, C>() data class Second<A, B, C>(val b: B) : Coproduct3<A, B, C>() data class Third<A, B, C>(val c: C) : Coproduct3<A, B, C>() Sum types - Coproduct val accountOp: Coproduct3<Account, PermissionError, AccessError Third(AccessError.ReadError) val result = accountOp.fold( { ScreenState.Content(it) }, { ScreenState.Error(it.toReadableText()) }, { ScreenState.Error(it.toReadableText()) } ) Target platform: JVM Running on Arrow v. 0.11.0-SNAPSHOT Running on Kotlin v. 1.3.50 Sum types - Union types (Meta) Arbitrary amount of representations. No hierarchy restrictions val accountOp: Union3<Account, PermissionError, AccessEr AccessError.ReadError val result = when (accountOp) { is Account -> ScreenState.Content(it) is PermissionError -> ScreenState.Error(it.toReadableT is AccessError -> ScreenState.Error(it.toReadableText( } Sum types - Union types (Meta) Powered by Arrow Meta. Arbitrary amount of representations. No hierarchy restrictions (can use system types ) val op: Union3<String, Int, Double> = 1 //no wrappers val case1: String? = op //null val case2: Int? = op //1 val case3: Double? = op //null Re~ned types Removes tests for type invariants. Compile time validated ✅ Powered by Arrow-Meta. @Refinement inline class TwitterHandle(val handle: String) { companion object : Refined<String, TwitterHandle> { override val target = ::TwitterHandle override val validate: String.() -> Map<String, Boolean mapOf( "Should start with '@'" to startsWith("@"), "Should have length <= 16" to (length <= 16), "Should have length > 2" to (length > 2), "Should not contain the word 'twitter'" to !con "Should not contain the word 'admin'" to !conta ) } Exercises 3. Immutable Data and Optics Why immutability? Leverages determinism. Avoid state mutation from everywhere. Restrict it to a single place.