Immutable?
Provability?
Side Effects?
An answer:
"What would code look like if we wrote only functions?"
Goto statements
Poor abstractions
Difficult to know about
"What would code look like if we wrote no exceptions?"
Exceptions solve the same problem that GOTO statements solve, skipping large blocks of code execution.
fun doThing() {
println("Final value:" + doBusinessLogic())
}
fun doBusinessLogic(): Int {
val x = doDatabaseThing()
return x + 1
}
fun doDatabaseThing(): Int {
// todo a real database connection
return 5
}
doThing()
import java.io.IOException
fun doDatabaseThing(): Int {
// todo a real database connection
// but we can't connect to a database, our function signature REQUIRES an Int, we can't
// meet that, we can throw!
throw IOException("Badness")
}
fun doBusinessLogic(): Int {
val x = doDatabaseThing()
// we can't try/catch the database exception here, if we do
// we get back an Exception, but our function signature REQUIRES an Int, so if we
// get an exception, we don't know how to add +1 to an Exception, so we allow
// the exception to bubble up
return x + 1
}
fun doThing() {
// okay we're finally at a level of abstraction that understands
// I wanted to do stuff, but it failed, I need to do something else
// I try/catch all Exception, the underlying type is meaningless to me
// no matter what happened to cause it to fail, it failed, I need to just display a message
try {
println("Final value:" + doBusinessLogic())
} catch(ex: Exception) {
println("Can't calculate value due to: " + ex.message)
}
}
doThing()
// Can't calculate value due to: Badness
// What if we don't want to throw?
fun doDatabaseThing(): Int? {
// what if we captured some notion of success or failure using the type system?
// if we can't get info from the Database, maybe a null type would do?
return null
}
fun doBusinessLogicOne(): Int? {
val x = doDatabaseThing()
// hmm, now our null leaked out here, no worry, we can just propagate the null
return if (x != null) { // forget kotlin syntactic sugar, we want to think of all languages
x + 1
} else {
x
}
}
fun doBusinessLogicTwo(x: Int?): Int? {
// now we need more null checks here :/
return if (x != null) {
x * 2
} else {
x
}
}
fun doThing() {
val finalValue = doBusinessLogicTwo(doBusinessLogic())
if (finalValue != null) {
println("Final value:" + finalValue)
} else {
println("Somewhere, an error!")
}
}
doThing()
fun doDatabaseThing(): Int? {
return null
}
fun doBusinessLogicOne(): Int? {
val x = doDatabaseThing() // maybe tight coupling to the db hurt us
// here, we still have a null
return if (x != null) {
x + 1
} else {
x
}
}
fun doBusinessLogicTwo(x: Int): Int {
return x * 2
}
fun doThing() {
val initialValue = doBusinessLogic()
val finalValue = if (initialValue != null) {
println("Final value: " + doBusinessLogicTwo(initialValue))
} else {
println("There was an error in businessLogic()!")
}
println("Final value:" + finalValue)
}
}
doThing()
fun doDatabaseThing(): Int? {
return null
}
fun doBusinessLogicOne(x: Int): Int { // look at these functions, so clean and easy again!
return x + 1
}
fun doBusinessLogicTwo(x: Int): Int {
return x * 2
}
fun doThing() {
val initialValue = doDatabaseThing() // as an aside, this is
// now closer to the "clean architecture"
val intermediate = if (initialValue != null) {
doBusinessLogicOne(initialValue)
} else {
// wait, what's going on here?!!?!
return
}
println("Final value: " + doBusinessLogicTwo(intermediate))
}
doThing()
fun doThing() {
val initialValue = doDatabaseThing()
val intermediate: Int? = if (initialValue != null) {
doBusinessLogicOne(initialValue)
} else { null }
if (intermediate != null) {
println("Final value: " + doBusinessLogicTwo(intermediate))
} else {
println("There was an error in businessLogic()!")
}
}
We can hand write this all, but let's use a library (Arrow) instead!
class DatabaseConnectionError : Exception()
fun doDatabaseThing(): Either<DatabaseConnectionError, Int> {
return Right(5)
}
So now we know exactly what doDatabaseThing is able to return.
"Right" means "did the right thing" "Left" means "something else" by convention.
class DatabaseConnectionError : Exception()
fun doDatabaseThing(): Either<DatabaseConnectionError, Int> {
return Right(5)
}
fun doBusinessLogicOne(x: Int): Int {
return x + 1
}
fun doBusinessLogicTwo(x: Int): Int {
return x * 2
}
fun doThing() {
val result = doDatabaseThing()
.map(::doBusinessLogicOne) // not our standard kotlin map
.map(::doBusinessLogicTwo)
result.bimap(
{ value -> println("There was an error!! it was: " + value.message )},
{ value -> println("Final value: " + value)}
)
}
Final value: 12
class DatabaseConnectionError(message: String) : Exception(message)
fun doDatabaseThing(): Either<DatabaseConnectionError, Int> {
return Left(DatabaseConnectionError("Badness"))
}
...
fun doThing() {
val result = doDatabaseThing()
.map(::doBusinessLogicOne) // not our standard kotlin map
.map(::doBusinessLogicTwo)
result.bimap(
{ value -> println("There was an error!! it was: " + value.message )},
{ value -> println("Final value: " + value)}
)
}
There was an error!! it was: Badness
fun main(args: Array<String>) {
val result = doThing()
result.bimap(
{ value -> println("There was an error!! it was: " + value.message )},
{ value -> println("Final value: " + value)}
)
}
class DatabaseConnectionError(message: String) : Exception(message)
fun doDatabaseThing(): Either<DatabaseConnectionError, Int> {
return Left(DatabaseConnectionError("Badness"))
}
fun doBusinessLogicOne(x: Int): Int {
return x + 1
}
fun doBusinessLogicTwo(x: Int): Int {
return x * 2
}
fun doThing(): Either<DatabaseConnectionError, Int> =
Either.monad<DatabaseConnectionError>().binding {
val dbvalue = doDatabaseThing().bind()
val resultOne = doBusinessLogicOne(dbvalue)
val resultTwo = doBusinessLogicTwo(resultOne)
yields(resultTwo)
}.ev()
We tried an experiment, what would code look like without exceptions?
There's a lot of nice stuff, but it gets clunky to code.
There's libraries that help us code this way.
The "functor" is something that happens when you try to write code this way, and it makes life easier.
https://github.com/arrow-kt/arrow/blob/3fdf22311dbdf07c68ee73ab05cd2f21561d6636/arrow-core/src/main/kotlin/arrow/core/Either.kt
http://adit.io/posts/2013-04-17-functors,_applicatives,_and_monads_in_pictures.html