Last active
October 1, 2019 21:21
-
-
Save Synesso/38517eff8c3cc5eb6ab03a69a1a45335 to your computer and use it in GitHub Desktop.
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
package ft.typesafety | |
import arrow.core.Option | |
import arrow.core.getOrElse | |
/** | |
* Use pattern matching and recursion. No vars, no loops, no overriding. | |
* | |
* `Option` is an implementation of optional functionality. | |
* | |
* We've made these exercises to give you greater insight into how an optional pattern | |
* might work in a functional language. | |
* | |
* When you see `Option` think: "It may exist, or it may not" | |
* | |
* There are two ways to construct an `Option`: | |
* | |
* `Some()` represents something that exists | |
* | |
* `None` represents something that doesn't exist | |
* | |
* We use `Option` in situations where there isn't certainty that a meaningful | |
* value will be returned to us. | |
* | |
* | |
* TODO - Rewrite these instructions for Kotlin | |
* | |
* The `get()` method on the key to value store `Map` is a great example of this. | |
* | |
* We expect `get()` to take a key and give us a value in return. | |
* | |
* But what happens when our Map doesn't know about the key we gave it? | |
* | |
* A Map here is the same as in any other language, | |
* we just need to tell it about the types we're working with. | |
* | |
* This is the type of the key | |
* | | |
* | This is the type of the value | |
* | | | |
* | | | |
* val myMap = mapOf<Int, String>( 1 to "one", 2 to "two", ...) | |
* | |
* | |
* When we call `get()` on Map we will always get back an `Option` type | |
* | |
* myMap.get(1) = Some("one") //The value exists and it's the string "one" | |
* | |
* myMap.get(0) = None //The value doesn't exist so we get None | |
* | |
* `Some("one")` and `None` are both of the type Option | |
* | |
* Since `Some` and `None` are the same type we can pattern match on them! | |
* | |
* We can have one set of logic when we get Some back and a different set | |
* of logic when we get `None` back! | |
* | |
* val mightBeSomething: Option<String> = myMap.get(3) | |
* | |
* val result: String = mightBeSomething match { | |
* case Some(string) => "I got a String back!" | |
* case None => "I got None back" | |
* } | |
* | |
* Good luck! | |
* | |
*/ | |
object OptionalExercises1 { | |
val config = mapOf("host" to "squareup.com", "port" to "8080") | |
fun getFromConfig(key: String): Option<String> = Option.fromNullable(config[key]) | |
fun lengthOfHost(): Option<Int> = getFromConfig("host").map { it.length } | |
fun portPlus1000(): Option<Int> = getFromConfig("port").map { it.toInt() + 1000 } | |
} | |
object OptionalExercises2 { | |
val hosts = mapOf("host1" to "squareup.com", "host2" to "test.squareup.com", "host3" to "netflix.com") | |
val envs = mapOf("squareup.com" to "prod", "test.squareup.com" to "test", "amazon.com" to "stage") | |
// Should return the env string if successful or "couldn't resolve" if unsuccessful | |
fun getEnvForHost(host: String): String = Option.fromNullable(hosts[host]) | |
.flatMap { Option.fromNullable(envs[it]) } | |
.getOrElse { "couldn't resolve" } | |
// See how many ways you can implement this. | |
// Will either return "Connected to <squareup host>" or "not connected" | |
fun connectToSquareupHostsOnly(host: String): String = Option.fromNullable(hosts[host]) | |
.filter { it.endsWith("squareup.com") } | |
.map { createConnection(it) } | |
.getOrElse { "not connected" } | |
private fun createConnection(domain: String): String = "connected to $domain" | |
} | |
/** | |
* Here we make the trait `Maybe`, which is our version of `Option` | |
* | |
* `Just` has the same behavior as `Some` | |
* `Nothing` has the same behavior as `None` | |
* | |
* We use this exercise to illustrate that we can create our own optional behavior | |
* with just a few functions. | |
* | |
*/ | |
object OptionalExercises3 { | |
interface Maybe<out A> | |
data class Just<A>(val get: A) : Maybe<A> | |
object Nothing : Maybe<kotlin.Nothing> | |
fun <A, B> flatMap(m: Maybe<A>, f: (A) -> Maybe<B>): Maybe<B> = when(m) { | |
is Just -> f(m.get) | |
else -> Nothing | |
} | |
fun <A, B> map(m: Maybe<A>, f: (A) -> B): Maybe<B> = when(m) { | |
is Just -> Just(f(m.get)) | |
else -> Nothing | |
} | |
fun <A, B> fold(m: Maybe<A>, default: () -> B, f: (A) -> B): B = when(m) { | |
is Just -> f(m.get) | |
else -> default() | |
} | |
fun <A> orElse(m: Maybe<A>, otherwise: () -> Maybe<A>): Maybe<A> = when(m) { | |
is Just -> m | |
else -> otherwise() | |
} | |
fun <A> orSome(m: Maybe<A>, default: () -> A): A = when(m) { | |
is Just -> m.get | |
else -> default() | |
} | |
fun <A, B, C> map2(m1: Maybe<A>, m2: Maybe<B>, f: (A, B) -> C): Maybe<C> = | |
flatMap(m1) { a -> map(m2) { b -> f(a, b) } } | |
fun <A> sequence(l: List<Maybe<A>>): Maybe<List<A>> = l.fold(Just(emptyList())) { acc, next -> | |
map2(acc, next) { xs, x -> xs.plus(x) } | |
} | |
fun <A, B> ap(m1: Maybe<A>, m2: Maybe<(A) -> B>): Maybe<B> = flatMap(m1) { a -> | |
map(m2) { f -> f(a) } | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment