Last active
March 18, 2018 16:53
-
-
Save to4iki/f7f8602fcae080d7c5f8a51954983a11 to your computer and use it in GitHub Desktop.
DI using `Reader` monad
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
/// 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) | |
} | |
} |
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
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) |
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
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