Skip to content

Instantly share code, notes, and snippets.

@elm4ward
Last active July 24, 2018 08:21
Show Gist options
  • Save elm4ward/8ae29ca20d87c4b3e53aaa7f5c74ca36 to your computer and use it in GitHub Desktop.
Save elm4ward/8ae29ca20d87c4b3e53aaa7f5c74ca36 to your computer and use it in GitHub Desktop.
import Foundation
// ---------------------
// Actions
// ---------------------
typealias Closure<S> = (inout S) -> Void
/// Our Actions are basically a Tree of Nodes
/// Data for multiple args
indirect enum GenericAction<S> {
case children(String, [GenericAction<S>])
case codable(String, Data?)
case closure(Closure<S>)
case hook(String, GenericAction<S>, Middleware<S>)
}
typealias Middleware<S> = (inout S, (GenericAction<S>, inout S) -> Void) -> Void
/// All Actions defined here.
/// The protocol can be extended and so on.
protocol ActionAlgebra {
associatedtype StateType
static func middleware(_: String, _: Self, _ : @escaping Middleware<StateType>) -> Self
static func state(_: @escaping ((inout StateType) -> Void)) -> Self
static func url(_: String) -> Self
static func and(_ a: Self, _ b: Self) -> Self
}
// ---------------------
// State
// ---------------------
/// This is a basic state, whatever
struct State {
var stringVal: String
var intValue: Int = 0
}
/// Helper init
extension State {
init(){
stringVal = ""
}
}
/// State Actions
protocol Actions: ActionAlgebra where StateType == State {}
// ---------------------
// String of Action
// ---------------------
/// Just a to String
extension String: Actions {
typealias StateType = State
static func and(_ a: String, _ b: String) -> String {
return "\(a) and \(b)"
}
static func state(_ c: @escaping ((inout State) -> Void)) -> String {
return "Change \(c)"
}
static func url(_ url: String) -> String {
return "Get Url '\(url)'"
}
static func middleware(_ name: String, _ then: String, _ mw: @escaping Middleware<State>) -> String {
return "\(name) > \(then)"
}
}
// ---------------------
// Node of Action
// ---------------------
let d = JSONDecoder()
let e = JSONEncoder()
typealias Action = GenericAction<State>
/// Returns a Node for an Action
extension GenericAction: ActionAlgebra where S == State {
typealias StateType = State
static func & (_ lhs: Action, _ rhs: Action) -> Action {
return and(lhs, rhs)
}
static func and(_ a: Action, _ b: Action) -> Action {
return .children("and", [a, b])
}
static func url(_ u: String) -> Action {
return .codable("url", try? e.encode([u]))
}
static func state(_ c: @escaping ((inout State) -> Void)) -> Action {
return .closure(c)
}
static func middleware(_ n: String, _ t: GenericAction<State>, _ mw: @escaping Middleware<State>) -> Action {
return .hook(n, t, mw)
}
}
// ---------------------
// Interpreter of Action Effect
// ---------------------
struct ActionInterpreter {
let run: (Action, inout State) -> Void
}
extension ActionInterpreter: Actions {
typealias StateType = State
static func and(_ a1: ActionInterpreter, _ a2: ActionInterpreter) -> ActionInterpreter {
return ActionInterpreter { a, s in
a1.run(a, &s)
a2.run(a, &s)
}
}
static func state(_ c: @escaping ((inout State) -> Void)) -> ActionInterpreter {
return ActionInterpreter { a, s in
c(&s)
}
}
static func middleware(_ n: String, _ a1: ActionInterpreter, _ mw: @escaping Middleware<State>) -> ActionInterpreter {
return ActionInterpreter { action, state in
mw(&state, a1.run)
}
}
static func url(_ u : String) -> ActionInterpreter {
return ActionInterpreter { _, _ in
/// TODO
}
}
}
// ---------------------
// Reduce and interpret
// ---------------------
/// Data Helper
func from<D: Decodable>(_ data: Data, at: Int) -> D {
return try! d.decode([D].self, from: data)[at]
}
/// Algebra Parser
func parse<E: Actions>(_ node: GenericAction<E.StateType>) -> E {
switch node {
case let .codable("url", data?):
return .url(from(data, at: 0))
case let .children("and", l):
return .and(parse(l[0]), parse(l[1]))
case let .hook(name, then, mw):
return .middleware(name, parse(then), mw)
case let .closure(c):
return .state(c)
default:
print("node failed:", node)
fatalError()
}
}
/// Reducer
func reduce(_ action: Action, _ state: inout State){
let s: ActionInterpreter = parse(action)
s.run(action, &state)
}
/// State and Actions
var state = State()
var increment: Action = .state {
$0.intValue = $0.intValue + 1
}
var test1: Action = .state {
$0.stringVal = "test1"
}
var test2: Action = .state {
$0.stringVal = "test2"
}
var network: Action = .url("url/")
func middleware(hook: @escaping (Action, inout State, (Action, inout State) -> Void) -> Void) -> (Action) -> Action {
return { action in
.middleware("log", action){ s, f in
hook(action, &s, f)
}
}
}
var logging = middleware { action, state, next in
let desc: String = parse(action)
print("will log on action:", desc)
print(" - before:", state)
next(action, &state)
print(" - after: ", state)
}
var highlight = middleware { action, state, next in
let desc: String = parse(action)
print(" ####", desc)
print(" - before:", state)
next(action, &state)
print(" - after: ", state)
print(" ####")
}
/// Reduce on with state
reduce(network, &state)
reduce(test1, &state)
reduce(logging(test2 & highlight(increment)), &state)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment