// nef:begin:header
/*
 layout: docs
 title: Suspending side effects
 */
// nef:end
// nef:begin:hidden
import Foundation
import Bow
import BowEffects
// nef:end
/*:
 # Suspending side effects
 
 {:.beginner}
 beginner
 
 In Functional Programming, functions must honor its mathematical definition: they must be total, deterministic and pure. However, this is not the situation in many cases; in fact, programs are useful when side-effects happen. The issue is not having side-effects *per se*, but the moment and the circumstances when they are executed. If they happen in the moment we invoke them, reasoning about their outcome becomes much more difficult. In addition to that, composition becomes harder, if not impossible, to achieve.
 
 ## Motivation
 
 Consider the following two functions:
 */
func greet(name: String) {
    print("Hello \(name)!")
}

func homePage(callback: @escaping (Either<Error, Data>) -> ()) {
    if let url = URL(string: "https://bow-swift.io") {
        URLSession.shared.dataTask(with: url) { data, _, error in
            if let data = data {
                callback(.right(data))
            } else if let error = error {
                callback(.left(error))
            }
        }.resume()
    }
}
/*:
 Function `greet` has a side effect (printing to the console) and does not produce any value, making it harder to compose this function with any other function. Function `homePage` does produce values, but they are returned through a callback as it is an asynchronous operation, making it difficult to be composed as well, besides having another side effect (network call).
 
 ## Creating an IO from a synchronous call
 
 Dealing with this type of functions is usual in our programming routine, either because we have some non-functional legacy code or because we are using libraries that do not provide a functional API. What can we do to overcome this issue?
 
 Bow Effects provides the IO data type. It is a data type that lets us suspend the execution of side effects, providing us a value that describes the side effect, but it has not been performed yet. That way, we can convert our programs into values that we can combine and compose.
 
 `IO` has a method called `invoke` that can help us suspend a side effect. For instance, the `greet` function above can be rewritten as:
 */
func greetIO(name: String) -> IO<Never, Void> {
    return IO.invoke { greet(name: name) }
}
/*:
 It may seem like we haven't done much, as we are still calling the old function, but its execution is deferred. It has been wrapped in an `IO` value that cannot produce errors (the `Never` type) and that do not produce any value (the `Void` type). Function `greetIO` is pure and provides a value as its output that can be composed with other `IO` values to make a bigger program.
 
 We can also wrap functions throwing errors in an IO using `invoke`. The function `findUser(by:)` below is impure:
 */
// nef:begin:hidden
struct User {}
func exists(id: String) -> Bool { return false }
func fetch(id: String) -> User { return User() }

enum DatabaseError: Error {
    case missing(id: String)
}
// nef:end
func findUser(by id: String) throws -> User {
    guard exists(id: id) else {
        throw DatabaseError.missing(id: id)
    }
    return fetch(id: id)
}
/*:
 Its pure counterpart can be written as:
 */
// Making use of the impure version
func findUserIO(by id: String) -> IO<DatabaseError, User> {
    return IO.invoke { try findUser(by: id) }
}

// Writing a new pure version
func findUser_fromEither(by id: String) -> IO<DatabaseError, User> {
    return IO.invokeEither {
        guard exists(id: id) else {
            return .left(.missing(id: id))
        }
        return .right(fetch(id: id))
    }
}
/*:
 The functional versions above have an additional benefit: they are explicit about the type of the errors that may happen during their execution.
 
 Besides `invoke`, `IO` has other methods to create a suspended side effect, like the `invokeEither` above, `invokeResult`, `invokeValidated` and `invokeTry`.
 
 ## Creating an IO from an asynchronous call
 
 Let's look now at the `homePage` function. It provides a generic (non-typed) error or a `Data` value, but it does it through a callback. How can an asynchronous call be suspended into an `IO` value?
 
 `IO` has a method called `async` that serves to this purpose:
 */
func homePageIO() -> IO<Error, Data> {
    return IO.async { callback in
        if let url = URL(string: "https://bow-swift.io") {
            URLSession.shared.dataTask(with: url) { data, _, error in
                if let data = data {
                    callback(.right(data))
                } else if let error = error {
                    callback(.left(error))
                }
            }.resume()
        }
    }^
}
/*:
 This new function provides an `IO<Error, Data>` value that suspend this asynchronous call and that can be transformed or composed.
 
 {:.intermediate}
 intermediate
 
 ## Creating an IO that depends on a context
 
 So far we have seen how to create an `IO` suspending a side effect. There are other cases where we may be interested on creating an `IO` that depends on a context that we still do not have.
 
 To this purpose, Bow Effects provides the `EnvIO<D, E, A>` type, which models an `IO<E, A>` that depends on some context `D`. This type is useful to have a sort of dependency injection, where we can write our programs without worrying too much about where the context is coming from, and later, when we can provide such context, we run the program.
 
 For instance, consider the following services: a network API to fetch users by their id and a database to save the user.
 */
protocol API {
    func getUser(by id: String) -> IO<Error, User>
    // ... Other methods ...
}

protocol Database {
    func save(user: User) -> IO<Error, Void>
    // ... Other methods ...
}
/*:
 We can create an Environment that provides these abstractions:
 */
struct Environment {
    let database: Database
    let api: API
}
/*:
 Now, let's say that we want to implement a function that gets a user from the API and stores it in the database. We can create an `EnvIO` that depends on the `Environment` that we created above to get the `API` and `Database`:
 */
func cacheUser(by id: String) -> EnvIO<Environment, Error, ()> {
    return EnvIO { environment in
        environment.api.getUser(by: id)
            .flatMap { user in environment.database.save(user: user) }
    }
}
// nef:begin:hidden
struct ProductionAPI: API {
    func getUser(by id: String) -> IO<Error, User> {
        return IO.never()^ // Provide an implementation
    }
}

struct TestAPI: API {
    func getUser(by id: String) -> IO<Error, User> {
        return IO.never()^ // Provide an implementation
    }
}

struct ProductionDatabase: Database {
    func save(user: User) -> IO<Error, Void> {
        return IO.never()^ // Provide an implementation
    }
}

struct TestDatabase: Database {
    func save(user: User) -> IO<Error, Void> {
        return IO.never()^ // Provide an implementation
    }
}
// nef:end
/*:
 This will allow us to run the program that we have created under different environments; e.g. we can provide different implementations for production and testing:
 */
// Providing production implementations
let prodEnv = Environment(database: ProductionDatabase(),
                          api: ProductionAPI())
cacheUser(by: "12345").provide(prodEnv)

// Providing testing implementations
let testEnv = Environment(database: TestDatabase(),
                          api: TestAPI())
cacheUser(by: "12345").provide(testEnv)
