A few things that popped up recently in discussions about mapping. Sharing them here for a wider discussion.
Multiargument Map
You cannot map a function with multiple arguments, even when the defaulted arguments mean an effective arity of 0 or 1:
["a", "b", "c"].map(print)
// Ambiguous reference to member 'print(_:separator:terminator:)'
func prettify(_ value: Int, _ radix: Int = 10) -> String {
let stringed = String(value, radix: radix)
return ("Value: \(stringed)")
}
[1, 2, 3].map(prettify)
// cannot convert value of type '(Int, Int) -> String' to expected argument type '(Int) -> _'
Could Swift work around this and allow mapping when a zero or one argument signature is generated due to defaulting?
Applying Methods
Mapping an instance method, gives you the partially applied method but does not execute them.
var array = ["a", "b", "c"].map(String.uppercased)
// [(Function), (Function), (Function)]
// Unexpected!
To get the expected result (["A", "B", "C"]
), you must either map ({ $0() })
or create something that does that for you. Here's one version:
extension Sequence {
/// Returns an array created by applying an instance
/// method to each element of a source sequence.
/// This method address the problem caused by mapping
/// instance methods:
///
/// ["a", "b", "c"].map(String.uppercased)
///
/// This statement returns `[(Function), (Function), (Function)]`,
/// which is essentially `[{ String.uppercased("a"), ... }]`
/// An instance method is `Type.method(instance)()`. Using
/// `mapApply` forces each partial result to execute and returns those
/// results, producing ["A", "B", "C"] as expected.
///
/// ["a", "b", "c"].mapApply(String.uppercased) // ["A", "B", "C"]
///
/// Thanks, Malcolm Jarvis, Gordon Fontenot
///
/// - Parameter transform: The partially applicable instance method
/// - Parameter element: The sequence element to be transformed by the
/// applied method
/// - Returns: An array containing the results of applying the method
/// to each value of the source sequence
public func mapApply<T>(_ transform: (_ element: Element) throws -> () -> T) rethrows -> [T] {
return try map(transform).map({ $0() })
}
Applying Keypaths
Similarly, Swift doesn't seem to allow you to map keypaths. Here's one approach:
extension Sequence {
/// Returns an array created by applying a keypath to
/// each element of a source sequence.
///
/// let keyPath = \String.count
/// ["hello", "the", "sailor"].map(keyPath) // 5, 3, 6
///
/// Thanks, Malcolm Jarvis, Gordon Fontenot
///
/// - Parameter keyPath: A keyPath on the `Element`
/// - Returns: An array containing the results of applying
/// the keyPath to each element of the source sequence.
public func map<T>(_ keyPath: KeyPath<Element, T>) -> [T] {
return map({ $0[keyPath: keyPath] })
}
}
Thoughts?