Swift Custom Operator(2)
April 19, 2019
| **[Swift: define F#-style pipe-forward ( | >) operator that evaluates from left to right.](https://gist.github.com/kristopherjohnson/ed97acf0bbe0013df8af)** |
// F#'s "pipe-forward" |> operator
//
// Also "Optional-chaining" operators |>! and |>&
//
// And adapters for standard library map/filter/sorted
infix operator |> { precedence 50 associativity left }
infix operator |>! { precedence 50 associativity left }
infix operator |>& { precedence 50 associativity left }
infix operator |>* { precedence 50 associativity left }
// Pipe forward: transform "x |> f" to "f(x)" and "x |> f |> g |> h" to "h(g(f(x)))"
public func |> <T,U>(lhs: T, rhs: T -> U) -> U {
return rhs(lhs)
}
// Unwrap the optional value on the left-hand side and apply the right-hand function
public func |>! <T,U>(lhs: T?, rhs: T -> U) -> U {
return rhs(lhs!)
}
// If Optional value on left is nil, return nil; otherwise unwrap it and
// apply the right-hand function to it.
//
// (It would be nice if we could name this |>?, but the ? character
// is not allowed in custom operators.)
public func |>& <T,U>(lhs: T?, rhs: T -> U) -> U? {
return lhs.map(rhs)
}
// Transform "x |>* (f, predicate)" to "f(x, predicate)"
public func |>* <A, B, C, T>(lhs: A, rhs: ((A, (B) -> C) -> T, (B) -> C)) -> T {
return (rhs.0)(lhs, rhs.1)
}
// Examples
// List of random numbers
let numbers = [67, 83, 4, 99, 22, 18, 21, 24, 23, 2, 86]
// Get the even numbers, sort them in descending order,
// and render the result as a comma-separated string
let result = ", ".join(numbers.filter { $0 % 2 == 0 }
.sorted { $1 < $0 }
.map { $0.description })
result
// Was easy for Array, let's try it as a Sequence with free functions
let seq = SequenceOf(numbers)
// All on one line
let seqResult =
", ".join(map(sorted(filter(seq){$0 % 2 == 0}){$1 < $0}){$0.description})
// Ugly/unreadable
let seqResultMultiLine =
", ".join(
map(
sorted(
filter(seq) { $0 % 2 == 0 }
) { $1 < $0 }
) { $0.description })
// Readable but verbose/noisy
let filteredNumbers = filter(seq) { $0 % 2 == 0 }
let sortedNumbers = sorted(filteredNumbers) { $1 < $0 }
let numbersAsStrings = map(sortedNumbers) { (n: Int) -> String in n.description }
let commaSeparated = ", ".join(numbersAsStrings)
// Using lazy()
let lazyResult =
", ".join(lazy(seq).filter({ $0 % 2 == 0 }).array
.sorted { $1 < $0 }
.map { $0.description })
// Can do this with pipe-forward, but need adapters for filter/sorted/map/join.
//
// Each adapter must be curried, and the sequence to be operated on must be the final argument.
// Curried adapter function for Swift Standard Library's filter() function
public func filteredWithPredicate<S : SequenceType>
(includeElement: (S.Generator.Element) -> Bool)
(source: S)
-> [S.Generator.Element]
{
return filter(source, includeElement)
}
// Curried adapter function for Swift Standard Library's sorted() function
public func sortedByPredicate<S : SequenceType>
(predicate: (S.Generator.Element, S.Generator.Element) -> Bool)
(source: S)
-> [S.Generator.Element]
{
return sorted(source, predicate)
}
// Curried adapter function for Swift Standard Library's map() function
public func mappedWithTransform<S: SequenceType, T>
(transform: (S.Generator.Element) -> T)
(source: S)
-> [T]
{
return map(source, transform)
}
let pipeResult =
seq |> filteredWithPredicate { $0 % 2 == 0 }
|> sortedByPredicate { $1 < $0 }
|> mappedWithTransform { $0.description }
|> String.join(", ")
// Instead of using the adapters, use equivalent closures
let closuresResult =
seq |> { s in filter(s) { $0 % 2 == 0 } }
|> { s in sorted(s) { $1 < $0 } }
|> { s in map(s) { $0.description } }
|> String.join(", ")
// Tuples
import Foundation
func diagonalLength(width: Double, height: Double) -> Double {
return sqrt(width * width + height * height)
}
let length = (3, 4) |> diagonalLength
func multiplyAndDivide(multiplier1: Double, multiplier2: Double, divisor: Double) -> Double {
return multiplier1 * multiplier2 / divisor
}
let value = (10, 20, 50) |> multiplyAndDivide
let evenNumbers = (seq, { $0 % 2 == 0 }) |> filter
// Optional chaining
let elements = [2, 4, 6, 8, 10]
func reportIndexOfValue(value: Int)(index: Int) -> String {
let message = "Found \(value) at index \(index)"
println(message)
return message
}
// Safe to use |>! if we know we'll find a value
find(elements, 6) |>! reportIndexOfValue(6) // "Found 6 at index 2"
// If left side may be nil, use |>&
find(elements, 3) |>& reportIndexOfValue(3) // nil
find(elements, 4) |>& reportIndexOfValue(4) // {Some "Found 4 at index 1"}
// Generic adapters
// The stuff below works, but it's VERY, VERY SLOW in a playground, so
// we've disabled it. Change the 'false' to 'true' if you want to test it.
#if false
// Rather than writing those "adapters" for the standard library
// filter, sorted, and map functions, we can apply a higher-level
// function to give them the signatures we need.
// Given a function with signature (A, B) -> T, return curried
// function with signature (B)(A) -> T
func pipeAdapted<A, B, T>(f: (A, B) -> T) -> (B -> A -> T) {
return { b in { a in f(a, b) } }
}
let pipeResultWithPipeAdapted =
seq |> pipeAdapted(filter)({ $0 % 2 == 0 })
|> pipeAdapted(sorted)({ $1 < $0 })
|> pipeAdapted(map)({ $0.description })
|> String.join(", ")
let pipeStarResult =
seq |>* (filter, { $0 % 2 == 0 })
|>* (sorted, { $1 < $0 })
|>* (map, { $0.description })
|> String.join(", ")
#endif