Last active
February 3, 2018 23:04
-
-
Save elm4ward/cde43b4c2efc301dee39292c962c2665 to your computer and use it in GitHub Desktop.
This file contains hidden or 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 mark = "####################################" | |
let chapter: (String) -> Void = { print("\n", mark, "# \($0)", mark, separator: "\n")} | |
// ---------------------------------------------------------------------------------------------------- | |
// MARK: - Look ma, no protocols | |
// | |
// http://elm4ward.github.io/swift/generics/type/erasure/2016/04/21/erase-em-all.html | |
// | |
// Erasing generics to life in freedom | |
// This is a follow up to the article about Protocols with Typealias | |
// | |
// 1. The struct with no protocols | |
// 2. Good generics | |
// 3. Bad generics | |
// 4. Get rid of the generic bastards | |
// 5. Combine different generic types after type erasure in an array | |
// 6. Hidden section: Both worlds? Seriously? | |
// ---------------------------------------------------------------------------------------------------- | |
// ---------------------------------------------------------------------------------------------------- | |
// MARK: - 1. The struct with no protocols | |
// ---------------------------------------------------------------------------------------------------- | |
// Instead of defining the protocols w/ associated types | |
// we can define e.g. a single struct for the stuff we wanna | |
// do with generics | |
// | |
// The functions in the struct define how the types lign up | |
struct Workflow<Fetched, ChangeTo, MergeTo> { | |
// no protocol: just a function | |
let change: (Fetched) -> ChangeTo | |
// no protocol: just a function | |
let merge: (MergeTo, ChangeTo) -> MergeTo | |
// initial value | |
let mergeStart: MergeTo | |
// provide argument and function returns happily a result | |
func run(fetch: [Fetched]) -> MergeTo { | |
// this was port of an protocol extension in the previous version | |
return fetch.map(change).reduce(mergeStart, merge) | |
} | |
} | |
// ---------------------------------------------------------------------------------------------------- | |
// MARK: - 2. Good Generics | |
// ---------------------------------------------------------------------------------------------------- | |
// lets define a closure to get the number | |
// of characters in a string | |
let countCharacters: (String) -> Int = { $0.characters.count } | |
// no need to specify the generics as all information about them is provided by args | |
// thats why the generics can be inferred on left and right side | |
let stringWorkflow = Workflow(change: countCharacters, merge: +, mergeStart: 0) | |
// who provides the info? | |
// | |
// Workflow<String, Int, Int> | |
// | | | | |
// | | ---> merge function | |
// | | | |
// ---------> change function | |
// in action | |
chapter("Workflow<String, Int, Int> in Action: stringWorkflow.run([\"a\", \"b\"])") | |
print(stringWorkflow.run(fetch: ["a", "b"])) | |
// ---------------------------------------------------------------------------------------------------- | |
// MARK: - 3. Bad generics | |
// ---------------------------------------------------------------------------------------------------- | |
// will not work - why? | |
// $0 is not defined as String | |
// so there is no solution for the type algorithm | |
// | |
// uncomment to see the error | |
// | |
// let workflow = Workflow( | |
// change: { $0.characters.count }, | |
// merge: (+), | |
// mergeStart: 0 | |
// ) | |
// | |
// possible solutions: | |
let stringWorkflow2 = Workflow<String, Int, Int>( | |
change: { $0.characters.count }, | |
merge: +, | |
mergeStart: 0 | |
) | |
// or | |
let stringWorkflow3: Workflow<String, Int, Int> = Workflow( | |
change: { $0.characters.count }, | |
merge: +, | |
mergeStart: 0 | |
) | |
// ---------------------------------------------------------------------------------------------------- | |
// MARK: - 4. Get rid of the generic bastards | |
// ---------------------------------------------------------------------------------------------------- | |
// type erasure struct | |
// without any generic information | |
struct AnyWorkflow { | |
// we dont know whats the result | |
// so all we can return is Any | |
let run: () -> Any | |
// in order to initialize with an generic type | |
// we need to make a generic init function. | |
// the information about the types is erased | |
// as all we do is storing the type in a | |
// function that returns Any | |
// | |
// 1. init is generic! | |
// 2. the type is not generic! | |
init<F, C, M>(workflow: Workflow<F, C, M>, run input: [F]) { | |
self.run = { | |
workflow.run(fetch: input) | |
} | |
} | |
} | |
// could be anything | |
// BUT: only valid calls will work | |
let any = AnyWorkflow(workflow: stringWorkflow, run: ["a", "b"]) | |
// in action | |
chapter("AnyWorkflow in Action: any.run()") | |
print(any.run()) | |
// At least the init is typesafe! | |
// | |
// uncomment for proof | |
// let failing = AnyWorkflow(workflow: stringWorkflow, run: [1, 2, 3]) | |
// ---------------------------------------------------------------------------------------------------- | |
// MARK: - 5. Combine different generic types after type erasure in an array | |
// ---------------------------------------------------------------------------------------------------- | |
// now a totally different workflow | |
let intWorkflow = Workflow( | |
change: { (int: Int) in int.description }, | |
merge: { [$0, $1].joined(separator: " - ") }, | |
mergeStart: "da ints" | |
) | |
// again we wrap it in AnyWorkflow | |
let any2 = AnyWorkflow(workflow: intWorkflow, run: [1,2,3,4]) | |
// Type erasure live and in action | |
// any and any2 consumed different workflows | |
// but we can put them in a list and run them! | |
let anyResults = [any, any2].map { $0.run() } | |
chapter("AnyWorkflow in Action: anyResults.dynamicType") | |
print(type(of: anyResults), anyResults) | |
for i in anyResults { | |
print("type is", type(of: i)) | |
} | |
// ---------------------------------------------------------------------------------------------------- | |
// MARK: - 6. Hidden section: Both worlds? Seriously? | |
// ---------------------------------------------------------------------------------------------------- | |
/// 1. protocol | |
protocol Fetchable { | |
associatedtype FetchedType | |
} | |
/// 2. protocol | |
protocol Changeable { | |
associatedtype ChangeResultType | |
} | |
/// 3. protocol | |
protocol Mergeable { | |
associatedtype MergeResultType | |
} | |
/// 4. conform to protocol | |
extension String: Fetchable { | |
typealias FetchedType = String | |
} | |
/// 5. conform to protocol | |
extension String: Changeable { | |
typealias ChangeResultType = Int | |
} | |
/// 6. conform to protocol | |
extension Int: Mergeable { | |
typealias MergeResultType = Int | |
} | |
/// the generics struct with protocols, associated types and constraints | |
struct ProtoWorkflow<F, C, M> where | |
F: Fetchable, | |
C: Changeable, | |
M: Mergeable, | |
F.FetchedType == C, | |
C.ChangeResultType == M | |
{ | |
// no protocol: just a function | |
let change: (F.FetchedType) -> C.ChangeResultType | |
// no protocol: just a function | |
let merge: (M.MergeResultType, C.ChangeResultType) -> M.MergeResultType | |
// initial value | |
let mergeStart: M.MergeResultType | |
// provide argument and function returns happily a result | |
func run(fetch: [F.FetchedType]) -> M.MergeResultType { | |
// this was port of an protocol extension in the previous version | |
return fetch.map(change).reduce(mergeStart, merge) | |
} | |
} | |
/// the generics can not be inferred ... | |
/// maybe just another reason to not use it that way | |
// ? | |
//let protoWorkflow = ProtoWorkflow<String, String, Int>(change: countCharacters, merge: +, mergeStart: 0) | |
// in action | |
//chapter("ProtoWorkflow<String, String, Int> in Action: protoWorkflow.run([\"a\", \"b\"])") | |
//print(protoWorkflow.run(fetch: ["a", "b"])) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
swift 3 update