Mohit S
January 24, 2018
1.4k

Functional Programming with Arrow

Discussion of data types and type classes available in the library called Arrow for Kotlin.

January 24, 2018

Transcript

1. Functional Programming
with Λrrow
Mohit Sarveiya

2. Functional Programming with Λrrow
• Data Types

• Optics

• Error Handling

• Type Classes

• Types of Polymorphism

• Type classes proposal for Kotlin

3. Optics
Iso
Lens

4. Optics
Iso
Lens

5. data class Point2D
data class Cords
(val x: Int, val y: Int)
Isomorphism
(val x: Int, val y: Int)

6. data class Point2D
data class Cords
(val x: Int, val y: Int)
Isomorphism
(val x: Int, val y: Int)

7. data class Point2D
data class Cords
(val x: Int, val y: Int)
Isomorphism
(val x: Int, val y: Int)
fun point2DToCords (point2D: Point2D): Cords {
return Cords(point2D.x, point2D.y)
}

8. data class Point2D
data class Cords
(val x: Int, val y: Int)
Isomorphism
(val x: Int, val y: Int)
fun cordsToPoint2D (cord: Cord): Point2D {
return Point2D(cord.x, cord.y)
}

9. Isomorphism
point2DToCords: Point2D "-> Cords
cordsToPoint2D: Cords "-> Point2D

10. Isomorphism
Source:
Focus:
f ≇ g = IdA
g ≇ f = IdS
f: "->
g: "->
S
A
S A
A S

11. Isomorphism
interface Iso {
/**
* Get the focus of a [Iso]
"*/
fun get(s: S): A
/**
* Get the modified focus of a [Iso]
"*/
fun reverseGet(b: B): T
}

12. Isomorphism
val pointIsoCords : Iso =
Iso(
get = { point !-> Cords(point.x, point.y) },
reverseGet = { cords !-> Point2D(cords.x, cords.y) }
)

13. Isomorphism
val point = Point2D(6, 10)
val cords: Cords = pointIsoCords.get(point)
"// Cords(6, 10)

14. Isomorphism
val cords = Cords(6, 10)
val point2D: Point2D = pointIsoCords.reverseGet(cords)
"// Point2D(6, 10)

15. data class Point2D
data class Tuple2
(val x: Int, val y: Int)
Isomorphism
(val x: Int, val y: Int)

16. data class Tuple2(val a: A, val b: B)
Isomorphism
data class Tuple3(val a: A,
val b: B,
val c: C)
data class Tuple4("..)

17. val pointIsoTuple: Iso> =
Iso(
get = { point !-> point.x toT point.y },
reverseGet = { tuple !-> Point2D(tuple.a, tuple.b) }
)
Isomorphism

18. var point2D = Point2D(1, 2)
val tuple2 = pointIsoTuple.get(point2D)
"// Tuple2(a=1, b=2)
val point2D = pointIsoTuple.reverseGet(tuple2)
"// Point2D(x=1, y=2)
Isomorphism

19. val pointIsoTuple = Iso(
get = { point !-> Tuple2(point.x, point.y) },
reverseGet = { tuple !-> Point2D(tuple.a, tuple.b) }
)
Generating Isomorphism
data class Point2D (val x: Int, val y: Int)

20. @Retention(SOURCE)
@Target(CLASS)
annotation class isos
Generating Isomorphism

21. Generating Isomorphism
@isos data class Point2D (val x: Int, val y: Int)

22. Optics
Iso
Lens

23. Lens
Source: S
Purpose: Set, Get, Copy an immutable data structure
through functional references.

24. Lens
Lens
Get : S "-> A
Set : A "-> (S "-> S)

25. Lens
data class Person(val name: String, val age: Int)

26. Lens
data class Person(val name: String, val age: Int)
val personNameLens : Lens = Lens(
get = { person !-> person.name },
set = { newName !-> {foo !-> foo.copy(name = newName)} }
)

27. Lens
val person = Person("John Doe", 100)
val personNameLens : Lens = Lens(
get = { person !-> person.name },
set = { newName !-> {foo !-> foo.copy(name = newName)} }
)

28. Lens
val person = Person("John Doe", 100)
val newPerson = personNameLens.set(person, "John Doe Jr”)
// Person(name=John Doe Jr, age=100)

29. Lens
data class Employee(val name: String, val company: Company)
data class Address(val city: String, val street: Street)
data class Street(val number: Int, val name: String)

30. Lens
val street = Street(1, "hacker Way”)
val employee = Employee("John Doe", company)

31. Lens
employee.copy(
company = employee.company.copy(
)
)
)
)

32. Lens
data class Street(val number: Int, val name: String)
data class Employee(val name: String, val company: Company)

33. val company: Company)
Lens
data class Employee(val name: String, val company: Company)
val employeeCompany: Lens =
Lens(get = { it.company },
set = { company !->
{ employee !->
employee.copy(company = company)
}
}
)

Lens
Lens(
{ company !->
}
})

35. data class Address(val city: String, val street: Street)
Lens
Lens(
get = { it.street },
set = { street !-> {
}
})
val street: Street)

36. data class Street(val number: Int, val name: String)
Lens
val streetName: Lens = Lens(
get = { it.name },
set = { name !-> { street !-> street.copy(name = name) } }
)
val name: String)

37. Lens
val employeeStreetName = employeeCompany compose
streetName
val employee = employeeStreetName.modify(
employee,
String"::capitalize)
// Employee(name=John Doe, ... name=Hacker Way))))

38. val company: Company)
Generating Lenses
val employeeCompany: Lens =
Lens(get = { it.company },
set = { company !->
{ employee !->
employee.copy(company = company)
}
}
)
data class Employee(val name: String, val company: Company)

39. Generating Lenses
@Retention(SOURCE)
@Target(CLASS)
annotation class lenses

40. Generating Lens
@lenses
data class Employee(val name: String,
val company: Company)

41. Error Handling
Option
Either
Try

42. Error Handling
Option
Either
Try

43. val books : List? = getBooks()

44. val books : List? = getBooks()
books"?.map { ""... }
books"?.flatMap { ""... }
books"?.fold(""...)

45. val book : Book? = getBookForId(1000)

46. val book : Book? = getBookForId(1000)
book"?.map { ""... }
book"?.flatMap { ""... }
book"?.fold(""...)

47. Option
A
Some None

48. sealed class Option {
object None : Option()
data class Some(val t: T) : Option()
}

49. val option : Option = getBookForId(1000)
fun getBookForId(id : Int) : Option {
val book : Book? = getAllBooksFromDB().find {
it.id "== id
}
return if (book "!= null) {
Some(book)
} else {
None
}
}

50. val option : Option = getBookForId(1000)
fun getBookForId(id : Int) : Option {
val book : Book? = getAllBooksFromDB().find {
it.id "== id
}
}
return if (book "!= null) {
Some(book)
} else {
None
}

51. val option : Option = getBookForId(1000)
fun T?.toOption(): Option =
if (this != null) {
Some(this)
} else {
None
}

52. val option : Option = getBookForId(1000)
fun getBookForId(id : Int) : Option {
return getAllBooksFromDB()
.find { it.id "== id }
.toOption()
}

53. val option : Option = getBookForId(1000)
val totalPrice: Option = option.map { book !->
book.price + tax
}

54. val option : Option = getBookForId(1000)
val description = option.fold(
ifEmpty = {
"No Description"
},
some = {
it.description
}
)

55. Error Handling
Option
Either
Try

56. fun reciprocal(i: Int): Double {
if (i "== 0)
throw IllegalArgumentException("Can’t be 0")
return 1.0 / i
}

57. throw IllegalArgumentException(“Can’t be 0")
fun reciprocal(i: Int): Double {
if (i == 0)
return 1.0 / i
}

58. try {
val reciprocal = reciprocal(2)
compute(reciprocal)
} catch (e: IllegalArgumentException) {
log("Failed to take reciprocal”)
}

59. Either
A
Left Right
B

60. sealed class Either {
data class Left(val a: A) : Either()
data class Right(val b: B) : Either()
}

61. fun reciprocal(i: Int): Double {
if (i == 0)
throw IllegalArgumentException(“Can’t be 0")
return 1.0 / i
}

62. fun reciprocal(i: Int): {
if (i == 0)
throw IllegalArgumentException(“Can’t be 0")
return 1.0 / I
}
Either

63. fun reciprocal(i: Int): {
return if (i == 0) {
} else {
1.0 / i
}
}
Either.left(IllegalArgumentException(“Can’t be 0")
Either

64. fun reciprocal(i: Int): {
return if (i == 0) {
} else {
}
}
Either.left(IllegalArgumentException(“Can’t be 0")
Either
Either.right(1.0 / i)

65. fun reciprocal(i: Int): {
return if (i == 0) {
} else {
}
}
Either.left(IllegalArgumentException(“Can’t be 0")
Either
Either.right(1.0 / i)

66. val either: Either = reciprocal(2)

67. val either: Either = reciprocal(2)
when(either) {
is Either.Left "-> log("Failed to take reciprocal")
is Either.Right "-> compute(either.b)
}

68. Error Handling
Option
Either
Try

69. fun reciprocal(i: Int): Double {
if (i == 0)
throw IllegalArgumentException(“Can’t be 0")
return 1.0 / i
}
External Library

70. var reciprocal : Double = try {
reciprocal(0)
} catch (e: IllegalArgumentException) {
1.0
}

71. val reciprocal = Try {
reciprocal(2)
}.getOrDefault {
1.0
}

72. Try {
reciprocal(0)
}.fold(
{ t: Throwable !->
log("Failed to take reciprocal", t)
},
{ num: Double !->
compute(num)
})

73. sealed class Try {
data class Failure(val e: Throwable) : Try()
data class Success(val value: A) : Try()
}

74. Adhoc Polymorphism & Type Classes

75. Type Classes
Type Constructors
Option, Try, Either, IO, Ior, Reader, State

76. Polymorphism

77. Parametric Polymorphism
fun head(a : List) : A {
return a[0]
}
• Same implementation for all types.

fun combineAll(a: Matrix, b: Matrix) : Matrix {
return doMatrixComputation()
}
• Same name with different implementation.

• Type Dependent.
fun combineAll(a: String, b: String) : String {
return a + b
}

79. Ad-hoc Polymorphism with Type Class
inline fun combineAll(
a: F, b: F,
ev: Monoid = monoidal()
): F {
return ev.combine(a, b)
}
• Polymorphism with Type Classes.

• How do I achieve this?

80. Define Type Class
interface Monoid {
fun combine(a: F, b: F): F
}

81. Define Type Class
interface Monoid {
fun combine(a: F, b: F): F
}
val monoidMap = mutableMapOf, Monoid"<*">>()

82. Define Instances of Type Class
val monoidMap = mutableMapOf, Monoid"<*">>()
monoidMap[String"::class.java] = object: Monoid {
override fun combine(a: String, b: String): String {
return a + b
}
}

83. Define Instances of Type Class
val monoidMap = mutableMapOf, Monoid"<*">>()
monoidMap[Matrix"::class.java] = object: Monoid {
override fun combine(a: Matrix, b: Matrix): Matrix {
}
}

84. Getting Instances of Type Class
val monoidMap = mutableMapOf, Monoid"<*">>()
inline fun monoidal(): Monoid {
return monoidMap[F"::class.java] as Monoid
}

85. Ad-hoc Polymorphism with Type Class
inline fun combineAll(
a: F, b: F,
ev: Monoid = monoidal()
): F {
return ev.combine(a, b)
}

86. Ad-hoc Polymorphism with Type Class
inline fun combineAll(
a: F, b: F,
ev: Monoid = monoidal()
): F {
return ev.combine(a, b)
}
combineAll(matrix, matrix)
combineAll("Hello", "World")

87. Type Classes in Λrrow
@typeclass
interface Functor : TC {
fun map(fa: HK, f: (A) -> B): HK
}

88. Type Classes Instance in Λrrow
@instance(Option"::class)
interface OptionFunctorInstance : Functor {
override fun map(
fa: OptionKind, f: kotlin.Function1
): Option =
fa.ev().map(f)
}

89. Type Classes Proposal
typeclass Monoid {
fun A.combine(b: A): A
fun empty(): A
}

90. Type Classes Proposal
package intext
instance object IntMonoid : Monoid {
fun Int.combine(b: Int): Int = this + b
fun empty(): Int = 0
}

91. Type Classes Proposal
import intext.IntMonoid
1.combine(2) "// 3
Int.empty() "// 0

92. Type Classes Proposal
import intext.IntMonoid
fun add(a: A, b: A): A given Monoid = a.combine(b)
add("a", "b") "// does not compile: No `String: Monoid`

93. Swift Extension Protocols
extension Int : Monoid {
static func e() "-> Int {
return 0
}
}
extension Bool : Monoid {
static func e() "-> Bool {
return false
}
}

95. Thank you!