Slide 1

Slide 1 text

AN ARROW TO THE KOTLIN

Slide 2

Slide 2 text

HELLO! My name is Saúl Díaz You can find me at @sefford or [email protected]

Slide 3

Slide 3 text

1. WHY OOP? A brief history of programming 3

Slide 4

Slide 4 text

1950 1960 1970 Machine assembly Procedural programming Functional programming OO programming 4

Slide 5

Slide 5 text

OOP was born on the premise that using concepts we intuitively know, we could split code better 5

Slide 6

Slide 6 text

THIS IS READABLE fun printAllAttendees(conference: Conference): List { val attendees = HashSet() for (talk in conference.talks) { for (attendee in talk.attendees) { attendees += attendee } } val names = ArrayList() for (attendee in attendees) { names.add( "${attendee.id} - ${attendee.name}") } return names } Borrowed from @pacoworks’ “State of fuctional Kotlin ecosystem 2018” "https://youtu.be/q_1xPYQLyaU" 6

Slide 7

Slide 7 text

THIS IS READABLE FAMILIAR fun printAllAttendees(conference: Conference): List { val attendees = HashSet() for (talk in conference.talks) { for (attendee in talk.attendees) { attendees += attendee } } val names = ArrayList() for (attendee in attendees) { names.add( "${attendee.id} - ${attendee.name}") } return names } Borrowed from @pacoworks’ “State of fuctional Kotlin ecosystem 2018” "https://youtu.be/q_1xPYQLyaU" 7

Slide 8

Slide 8 text

100% of bugs are caused by source code 8

Slide 9

Slide 9 text

THIS CAN BE ALSO FAMILIAR fun printParticipants(conference: Conference): List { return conference.talks.map { talk -> talk.attendees } .flatten() .toSet() .map { attendee -> "${attendee.id} - ${attendee.name}" } } 9

Slide 10

Slide 10 text

We as professional developers are only as good as the tools we know how to use 10

Slide 11

Slide 11 text

2. FP PRINCIPLES And why you should be adopting them right now on OOP 11

Slide 12

Slide 12 text

INMUTABILITY 12

Slide 13

Slide 13 text

WATCH OUT FOR MUTABILITY fun `problems_of_mutability`() { val origin = mutableListOf("0") mutability(origin) // Whoops, I accidentally cleaned origin too :( } fun mutability(origin: MutableList) { val shallow = origin // Do whatever shallow.clear() } 13

Slide 14

Slide 14 text

WATCH OUT FOR MUTABILITY fun `problems_of_mutability`() { val origin = listOf("0") mutability(origin) // Origin is safe } fun mutability(origin: List) { val deep = origin.toMutableList() // Do whatever deep.clear() } 14

Slide 15

Slide 15 text

PURITY 15

Slide 16

Slide 16 text

NOPE fun incrementImpure() : Int { this.log.append("Increased by 1")// This is a side effect return this.current + 1 } 16

Slide 17

Slide 17 text

CORNER SIDE EFFECTS fun log(){ this.log.append("Increased by 1") } fun incrementPure() : Int { return this.current + 1 } 17

Slide 18

Slide 18 text

HIGHER ORDER 18

Slide 19

Slide 19 text

FIRST CLASS CITIZENS fun funWithFunctions() { var list = listOf("0", "1","2","3","4","5") // Effective function polymorphism morphList(list, ::firstFive) morphList(list, ::reverse) morphList(list, ::empty) } fun morphList(list: List< E>, transform: (List< E>) -> List< E>): List { return transform(list) } fun firstFive(list: List< E>): List = list.subList( 0, 5) fun reverse(list : List< E>) : List = list.reversed() fun empty(list : List< E>) : List = return emptyList() 19

Slide 20

Slide 20 text

FIRST CLASS CITIZENS fun funWithFunctions() { var list = listOf("0", "1", "2", "3", "4", "5") morphList(list, functionFactory(System.currentTimeMillis())) } fun morphList(list: List< E>, transform: (List< E>) -> List< E>): List { return transform(list) } fun functionFactory(time: Long): (List< E>) -> List< E> { return when(time % 3) { 0L -> ::firstFive 1L -> ::reverse else -> ::empty } } 20

Slide 21

Slide 21 text

3. WORKING WITH FP Let’s make a simple example 21

Slide 22

Slide 22 text

Place your screenshot here TEST PROJECT 22

Slide 23

Slide 23 text

REGISTRATION LOGIC Validate input data Perform register Perform login Notify error Get into the app Retrieve input data 23

Slide 24

Slide 24 text

REPRESENTING ABSENCE Requires "io.arrow-kt:arrow-core" val schrodinger = Option(Cat()) val schrodinger = Cat().some() // Some(Cat()) val schrodinger = Option.fromNullable(catRoulette()) val schrodinger = None 24

Slide 25

Slide 25 text

ARROW DATA TYPES class Nullable(val value : T?) class NotNullable( val value : T) // NotNullable(null) == BOOOM! 25

Slide 26

Slide 26 text

ARROW DATA TYPES 26

Slide 27

Slide 27 text

ARROW DATA TYPES Option 27

Slide 28

Slide 28 text

MANIPULATING DATA TYPES ??? Option 28

Slide 29

Slide 29 text

MANIPULATING DATA TYPES ??? Option ??? Option val stringifiedCat = schrodinger.map { cat -> cat.toString() } 29

Slide 30

Slide 30 text

MANIPULATING DATATYPES Option val value = stringifiedCat.fold( { "" }) { it } "" "Schrodinger" 30

Slide 31

Slide 31 text

MANIPULATING DATATYPES Option val value = stringifiedCat.fold( { "" }) { it } "" "Schrodinger" Left or Error case Right or Success case 31

Slide 32

Slide 32 text

OUR FIRST STEP IS DONE fun onRegisterClicked() { val username = Option.fromNullable( etUsername) .map { editText -> editText.text } .map { text -> text.toString() } .getOrElse { "" } val password = Option.fromNullable( etPassword) .map { editText -> editText.text } .map { text -> text.toString() } .getOrElse { "" } } 32

Slide 33

Slide 33 text

Place your screenshot here VALIDATING INPUTS 33

Slide 34

Slide 34 text

VALIDATING INPUTS Requires "io.arrow-kt:arrow-data" val valid = Valid("Schrodinger") val valid = "Schrodinger".valid() val invalid = Invalid("Error") val invalid = "Error".invalid() 34

Slide 35

Slide 35 text

VALIDATING INPUTS Requires "io.arrow-kt:arrow-data" val valid = Valid("Schrodinger") val valid = "Schrodinger".validNel() val invalid = Invalid(NonEmptyList.just("Error")) val invalid = "Error".invalidNel() 35

Slide 36

Slide 36 text

Let’s make a detour for a minute 36

Slide 37

Slide 37 text

ERROR HANDLING val errorTry: Try = Try { throw Exception("BOOOOM!") } // val errorValidated: ValidatedNel = Exception( "BOOOOM!").invalidNel() val errorEither: Either = Exception( "BOOOOM!").left() 37

Slide 38

Slide 38 text

ERROR HANDLING COMPARED Option Try Validated Either Sucess case Any Any Any Any Error case Nothing Throwable Any Any TL;DR Error case is absence An exception is good enough Accumulates errors with Nel flavor You need to control the type of error 38

Slide 39

Slide 39 text

APPLICATIVE BUILDER Requires "io.arrow-kt:arrow-instances-data" Empty Validation Length Validation Nth Validation ... Input Output "Password" "Schrodinger" 39

Slide 40

Slide 40 text

APPLICATIVE BUILDER Validated.applicativeError(NonEmptyList.semigroup()) .tupled(isUsernameBlank(username), hasUsernameValidCharacters(username), isPasswordBlank(password), isPasswordLongEnough(password), isPasswordStrongEnough(password)) .fix() 40

Slide 41

Slide 41 text

APPLICATIVE BUILDER .fix() .fold({ errorList -> errorList.all.forEach { error -> when (error) { // Notify all the accumulated errors is FormError.EmptyUsername -> { } is LoginViewTest.FormError.InvalidUsername -> {} is LoginViewTest.FormError.EmptyPassword -> {} is LoginViewTest.FormError.LengthInsufficient -> {} is LoginViewTest.FormError.RequiresSpecialCharacters -> {} } } }) { result -> // User and password got validated, proceed to next stage } 41

Slide 42

Slide 42 text

Place your screenshot here BACKGROUND WORK 42

Slide 43

Slide 43 text

BACKGROUND WORK Requires "io.arrow-kt:arrow-effects" IO.async { api.register(username, password) } .unsafeRunAsync { result: Either -> result.fold({ error -> view?.notifyError(error) }) { success -> view?.notifySucess(success) } } 43

Slide 44

Slide 44 text

COROUTINES INTEGRATION Requires "io.arrow-kt:arrow-effects-coroutines" DeferredK(CommonPool) { api.register(username, password) } .unsafeRunAsync { result: Either -> result.fold({ error -> view?.notifyError(error) }) { success -> view?.notifySucess(success) } } 44

Slide 45

Slide 45 text

MONAD FUNCTOR ForDeferredK extensions { binding { DeferredK(CommonPool) { api.register(user, pass) }.bind() DeferredK(CommonPool) { api.login(user, pass) }.bind() }.fix() .unsafeRunAsync { result -> DeferredK( UI) { result.fold( { ex -> view?.notifyRegisterError(ex) }) { view?.navigateToHome() } } } } 45

Slide 46

Slide 46 text

Monad Fail fast error handling ADVANCED ERROR HANDLING COMPARED Applicative Accumulative error handling 46

Slide 47

Slide 47 text

THE FP RESULT val username = Option.fromNullable(view?. username).getOrElse { "" } val password = Option.fromNullable(view?. password).getOrElse { "" } Validated. applicativeError(NonEmptyList. semigroup()) .tupled(isUsernameBlank(username), hasUsernameValidCharacters(username), isPasswordBlank(password), isPasswordLongEnough(password), isPasswordStrongEnough(password)) . fix() .fold( { errorList -> errorList. all.forEach { error -> when (error) { is FormError.EmptyUsername -> { ... } is LoginViewTest.FormError.InvalidUsername -> { ... } is LoginViewTest.FormError.EmptyPassword -> {... } is LoginViewTest.FormError.LengthInsufficient -> {...} is LoginViewTest.FormError.RequiresSpecialCharacters -> {...} } } }) { result -> ForDeferredK extensions { binding { DeferredK(CommonPool) { api.register(username, password) }.bind() DeferredK(CommonPool) { api.login(username, password) }.bind() }.fix() . unsafeRunAsync { result -> DeferredK( UI) {result.fold( { error -> view?.notifyRegisterError(error) }) { view?.navigateToHome() } } } } } 47

Slide 48

Slide 48 text

LET’S GIVE IT AN OOP TWIST! 48

Slide 49

Slide 49 text

EXTRACT VALIDATOR CLASS class FormValidator( val username: String, val password: String) { fun validate(): Validated, Tuple2> { return Validated.applicativeError(NonEmptyList. semigroup()) .tupled(isUsernameBlank( username), hasUsernameValidCharacters( username), isPasswordBlank( password), isPasswordLongEnough( password), isPasswordStrongEnough( password)) . fix() .map { result -> Tuple2(username, password) } } // … } 49

Slide 50

Slide 50 text

BUILD USE CASES open class UseCase(val logic: (P) -> R) { fun defer(thread: CoroutineDispatcher = CommonPool, params: P): DeferredK< R> { return DeferredK.async(thread) { logic.invoke(params) } } } class RegisterUser( val api: Api) : UseCase, Unit>( { (username, password) -> api.register(username, password) }) class LoginUser(val api: Api) : UseCase, String>( { (username, password) -> api.login(username, password) }) fun DeferredK< A>.executeAndReturnOnUi(onError: (Throwable) -> Unit, onSuccess: ( A) -> Unit) { this.unsafeRunAsync { result -> DeferredK(UI) { result.fold(onError, onSuccess) } } } 50

Slide 51

Slide 51 text

A LITTLE OF EXTRACT METHODS private fun showFormErrors(errors: List) { errors.forEach { error -> when (error) { is FormError.EmptyUsername -> { ... } is FormError.InvalidUsername -> { ... } is FormError.EmptyPassword -> { ... } is FormError.LengthInsufficient -> { ... } is FormError.RequiresSpecialCharacters -> { ... } } } } 51

Slide 52

Slide 52 text

THE FINAL RESULT val username = Option.fromNullable(view?. username).getOrElse { "" } val password = Option.fromNullable(view?. password).getOrElse { "" } FormValidator(username, password) .validate() .fold( { errorList -> view?.showFormErrors(errorList. all) }) { (username, password) -> Registration(api) .defer(Tuple2(username, password)) . executeAndReturnOnUi({ error -> view?.notifyRegisterError(ex) }) { token -> view?.navigateToHome() } } } 52

Slide 53

Slide 53 text

FP Null handling Independent computation Sucess/Error result handling WE HAVE INTRODUCED A LOT OF TECHNIQUES OOP SOLID principles Extension functions Deferred validation Clean architecture 53

Slide 54

Slide 54 text

“ But FP (and Arrow) has a lot of more things to offer! 54

Slide 55

Slide 55 text

OPTICS Providing inmutable models and the way to modify them @optics data class User(val address: Address) { companion object } @optics data class Address(val city: City) { companion object } @optics data class City(val name: String) { companion object } fun uppercaseCity() { val user = User(Address(City( "Łódź"))) // In OOP val modifiedUserInOOP = user.copy( address = user. address.copy( city = City(user. address.city.name.toUpperCase() ) ) ) // With FP and Arrow val modifiedUserWithArrow = User. address.city.modify(user) { city -> City(city.name.toUpperCase()) } } 55

Slide 56

Slide 56 text

HIGHER KIND Writing generic code which can be executed polymorphically on runtime abstract class UseCase( val logic: (P) -> R) { fun defer(thread: CoroutineDispatcher = CommonPool, params: P): DeferredK { return DeferredK.async(thread) { logic.invoke(params) } } } class RegisterUser(val api: Api) : UseCase, Unit>( /.../) 56

Slide 57

Slide 57 text

HIGHER KIND Writing generic code which can be executed polymorphically on runtime abstract class HKindUseCase( val async: Async, val logic: (P) -> R) : Async by async { fun defer(thread: CoroutineDispatcher = CommonPool, params: P): Kind { return async(thread) { logic.invoke(params) } } } class Register(async: Async, val api: Api) : HKindUseCase, Unit,F> (async, /.../}) fun testRegister() { Register(DeferredK. async(), Api()) Register(IO. async(), Api()) Register(FluxK. async(), Api()) Register(ObservableK. async(), Api()) } 57

Slide 58

Slide 58 text

▸ The more tools you know, the better dev you become ▸ No silver bullets, approach each problems with the right tools ▸ Don’t be dogmatic WRAPPING UP 58

Slide 59

Slide 59 text

THIS CAN BE READABLE val username = Option.fromNullable(view?. username).getOrElse { "" } val password = Option.fromNullable(view?. password).getOrElse { "" } Validated. applicativeError(NonEmptyList. semigroup()) .tupled(isUsernameBlank(username), hasUsernameValidCharacters(username), isPasswordBlank(password), isPasswordLongEnough(password), isPasswordStrongEnough(password)) . fix() .fold( { errorList -> errorList. all.forEach { error -> when (error) { is FormError.EmptyUsername -> { ... } is LoginViewTest.FormError.InvalidUsername -> { ... } is LoginViewTest.FormError.EmptyPassword -> {... } is LoginViewTest.FormError.LengthInsufficient -> {...} is LoginViewTest.FormError.RequiresSpecialCharacters -> {...} } } }) { result -> ForDeferredK extensions { binding { DeferredK(CommonPool) { api.register(username, password) }.bind() DeferredK(CommonPool) { api.login(username, password) }.bind() }.fix() . unsafeRunAsync { result -> DeferredK( UI) {result.fold( { error -> view?.notifyRegisterError(error) }) { view?.navigateToHome() } } } } } 59

Slide 60

Slide 60 text

BUT THIS IS INTUITIVE val username = Option.fromNullable(view?. username).getOrElse { "" } val password = Option.fromNullable(view?. password).getOrElse { "" } FormValidator(username, password) .validate() .fold( { errorList -> view?.showFormErrors(errorList. all) }) { (username, password) -> Registration(api) .defer(Tuple2(username, password)) . executeAndReturnOnUi({ error -> view?.notifyRegisterError(ex) }) { token -> view?.navigateToHome() } } } 60

Slide 61

Slide 61 text

THANKS! Any questions? You can find me at @sefford & [email protected]

Slide 62

Slide 62 text

CREDITS Special thanks to all the people who made this presentation possible: ▸ Jorge Castillo for proofreading this talk ▸ Francisco Estévez & Raúl Raja for their mentorship ▸ The Arrow team ▸ Arrow documentation ▸ Bow is Swift’s implementation of Arrow by Tomás Ruiz López ▸ Presentation template by SlidesCarnival ▸ https://speakerdeck.com/sefford/an-arrow-to-the-kotlin