Skip to content

Instantly share code, notes, and snippets.

@elm4ward
Last active February 3, 2018 23:04
Show Gist options
  • Save elm4ward/cde43b4c2efc301dee39292c962c2665 to your computer and use it in GitHub Desktop.
Save elm4ward/cde43b4c2efc301dee39292c962c2665 to your computer and use it in GitHub Desktop.
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"]))
@elm4ward
Copy link
Author

swift 3 update

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment