Skip to content

Instantly share code, notes, and snippets.

@to4iki
Last active March 18, 2018 16:53
Show Gist options
  • Save to4iki/f7f8602fcae080d7c5f8a51954983a11 to your computer and use it in GitHub Desktop.
Save to4iki/f7f8602fcae080d7c5f8a51954983a11 to your computer and use it in GitHub Desktop.
DI using `Reader` monad
/// See also:
/// - https://github.com/RxSwiftCommunity/Action
/// - https://github.com/ukitaka/RealmIO
/// - https://medium.com/@JorgeCastilloPr/kotlin-dependency-injection-with-the-reader-monad-7d52f94a482e
public class Reader<Input, Element> {
public typealias WorkFactory = (Input) -> Element
private let workFactory: WorkFactory
public init(_ workFactory: @escaping WorkFactory) {
self.workFactory = workFactory
}
public func execute(_ value: Input) -> Element {
return workFactory(value)
}
public func map<T>(_ transform: @escaping (Element) -> T) -> Reader<Input, T> {
return Reader<Input, T> { input in
transform(self.execute(input))
}
}
public func flatMap<T>(_ transform: @escaping (Element) -> Reader<Input, T>) -> Reader<Input, T> {
return Reader<Input, T> { input in
transform(self.execute(input)).execute(input)
}
}
public func flatMapConcat<Input2, T>(_ transform: @escaping (Element) -> Reader<Input2, T>) -> Reader<(Input, Input2), T> {
return Reader<(Input, Input2), T> { input in
transform(self.execute(input.0)).execute(input.1)
}
}
public func zip<Element2>(_ other: Reader<Input, Element2>) -> Reader<Input, (Element, Element2)> {
return self.flatMap { element in
other.map { element2 in (element, element2) }
}
}
public static func zip<Input, Element, Element2>(_ r1: Reader<Input, Element>, _ r2: Reader<Input, Element2>) -> Reader<Input, (Element, Element2)> {
return r1.zip(r2)
}
}
let addTwo: (Int) -> Int = { $0 + 2 }
let twice: (Int) -> Int = { $0 * 2 }
let multipleAction: (Int) -> Reader<Int, Int> = { i1 in Reader({ i2 in i1 * i2 }) }
let intToString: (Int) -> String = { "\($0)" }
Reader(addTwo) // `5` + 2 = 7
.map(twice) // 7 * 2 = 14
.flatMap(multipleAction) // 14 * `5` = 70
.map(intToString)
.map { "value is \($0)" }
.execute(5)
struct User {
typealias Id = String
let id: Id
}
struct Animal {
let name: String
}
protocol UserRepositoryType {
func fetchUser(with id: User.Id) -> User?
func fetchFreiends(with id: User.Id) -> [User]
}
struct UserRepository: UserRepositoryType {
func fetchUser(with id: User.Id) -> User? {
return User(id: "\(id)")
}
func fetchFreiends(with id: User.Id) -> [User] {
return "abc".map { User(id: "\(id)->\($0)") }
}
}
struct MockUserRepository: UserRepositoryType {
func fetchUser(with id: User.Id) -> User? {
return User(id: "DummyUser")
}
func fetchFreiends(with id: User.Id) -> [User] {
return []
}
}
protocol AnimaRepositoryType {
func fetch(with user: User.Id) -> [Animal]
}
struct AnimaRepository: AnimaRepositoryType {
func fetch(with id: User.Id) -> [Animal] {
return (1...3).map { Animal(name: "\(id)~>\($0)") }
}
}
struct MockAnimaRepository: AnimaRepositoryType {
func fetch(with id: User.Id) -> [Animal] {
return [Animal(name: "\(id)~>DummyAnimal")]
}
}
struct UserAction {
static let shared = UserAction()
private init () {}
func fetch(with id: String) -> Reader<UserRepositoryType, User?> {
return Reader { repository in
repository.fetchUser(with: id)
}
}
func fetchFreiends(with id: String) -> Reader<UserRepositoryType, [User]> {
return Reader { repository in
repository.fetchFreiends(with: id)
}
}
}
struct AnimalAction {
static let shared = AnimalAction()
private init () {}
func fetch(with id: User.Id) -> Reader<AnimaRepositoryType, [Animal]> {
return Reader { repository in
repository.fetch(with: id)
}
}
}
// MARK: - execute
let userAction = UserAction.shared
let animalAction = AnimalAction.shared
let fetchFreiendsAction = userAction.fetch(with: "hoge")
.map { $0?.id ?? "" }
.flatMap(userAction.fetchFreiends)
fetchFreiendsAction.execute(UserRepository())
// [{id "hoge->a"}, {id "hoge->b"}, {id "hoge->c"}]
fetchFreiendsAction.execute(MockUserRepository())
// []
let fetchAnimalAction = userAction.fetch(with: "fuga")
.map { $0?.id ?? "" }
.flatMapConcat(animalAction.fetch)
fetchAnimalAction.execute((UserRepository(), AnimaRepository()))
// [{name "fuga~>1"}, {name "fuga~>2"}, {name "fuga~>3"}]
fetchAnimalAction.execute((MockUserRepository(), MockAnimaRepository()))
// [{name "DummyUser~>DummyAnimal"}]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment