I bumped into this example of a cool Swift technique without an explanation of how it works or what it does, and even though I've written a decent amount of Swift code, this is so full of intricate Swiftisms it's currently twisting my head around.
If there's someone proficient enough with Swift who doesn't mind dissecting it and discerning its purpose, it would really increase my grasp of the language and its more powerful features, and probably enlighten others as well.
extension Array {
var match : (head: T, tail: [T])? {
return (count > 0) ? (self[0], Array(self[1..<count])) : nil
}
}
func map<A, B>(f: A -> B, arr: [A]) -> [B] {
if let (head, tail) = arr.match {
return [f(head)] + map(f, tail)
} else {
return []
}
}
The extension to the Array type defines a new computed property called match. This property is of type (head: T, tail: [T])?, so an optional tuple where the first element (named head) is of type T and the second element (named tail) is of type Array of T. T is just the generic type for the type of the element that is stored inside of the array (Since Array is a generic collection itself).
var match : (head: T, tail: [T])? {
return (count > 0) ? (self[0], Array(self[1..<count])) : nil
}
This computed property either returns nil, if the Array doesn't contain any elements, or it returns a tuple containing the first element of the list (self[0] and the tail of the list (everything except for the first element): Array(self[1..<count]).
Now that array have this computed property we can make use of it to recursively call a function f on the elements of an array and return a new Array that contains the results of those function calls. This is what a map function does:
func map<A, B>(f: A -> B, arr: [A]) -> [B] {
if let (head, tail) = arr.match {
return [f(head)] + map(f, tail)
} else {
return []
}
}
The mapping function f is of type A -> B which means that it maps elements of type A to elements of type B, therefore map has to be passed an array of type [A]. Then, using the match variable and optional binding we can call f on the head of the list [f(head)] and put it inside of a new list and then append the result of the recursive map call for the rest of the array elements map(f, tail).
To understand the map function they're trying to implement better:
Related
What does Element mean when writing an extension on Array?
like in this example:
extension Array {
func reduce<T>(_ initial: T, combine: (T, Element) -> T) -> T {
var result = initial
for x in self {
result = combine(result, x)
}
return result
}
}
The combine parameter is a function which takes a parameter of type T and Element. The Element is the actual Element of/in the array.
For example, this is an array of Int elements:
let arr = [1,2,5,77]
In reduce, initial is of type T. This is the staring value for the mapping you are about to perform.
In combine, T is like your starting value for each subsequent step of combing the next Element to produce another value of type T which will be used as the next T in combine, and so and so forth until the entire array has been processed.
If you were using a default use of reduce such as:
arr.reduce(0, +)
You can see that in this case, T and Element would both be of the same type, Int.
However, I could have a custom object that the array is of, and my combine is defining how to get the running total. If you had something like this:
struct Thing {
var val1: String
var val2: Int
}
let thingArray = //...define some Things in an array
You could use reduce and define your own combine function to return the sum of all the val2 values. In this case, T would be an Int, and Element would be Thing.
I have the following code in my project:
extension Array {
func unzip<A, B>() -> ([A], [B]) where Element == (A, B) {
reduce(into: ([], [])) {
$0.0.append($1.0)
$0.1.append($1.1)
}
}
}
This compiles and works exactly as I'd like it to - any instance of an array of tuples can be "unzipped" into two arrays of types that match the first and second elements of the tuple.
But for style reasons I would prefer for this to be a computed property instead of a function, so it would be executed as tupleArray.unzipped instead of tupleArray.unzip(). How do I do that?
I have an extension to Dictionary that adds map, flatMap, and filter. For the most part it's functional, but I'm unhappy with how the arguments to the transform and predicate functions must be specified.
First, the extension itself:
extension Dictionary {
init<S:SequenceType where S.Generator.Element == Element>(elements: S) {
self.init()
for element in elements {
self[element.0] = element.1
}
}
func filter(#noescape predicate:(Key, Value) throws -> Bool ) rethrows -> [Key:Value] {
return [Key:Value](elements:try lazy.filter({
return try predicate($0.0, $0.1)
}))
}
}
Now then, since the predicate argument is declared as predicate:(Key, Value), I would expect the following to work:
["a":1, "b":2].filter { $0 == "a" }
however, I have to actually use:
["a":1, "b":2].filter { $0.0 == "a" }
This is kind of confusing to use since the declaration implies that there are two arguments to the predicate when it's actually being passed as a single tuple argument with 2 values instead.
Obviously, I could change the filter function declaration to take a single argument (predicate:(Element)), but I really prefer it to take two explicitly separate arguments.
Any ideas on how I can actually get the function to take two arguments?
When you are using closures without type declaration, the compiler has to infer the type. If you are using only $0 and not $1, the compiler thinks that you are declaring a closure with only one parameter.
This closure then cannot be matched to your filter function. Simple fix:
let result = ["a":1, "b":2].filter { (k, _) in k == "a" }
Now also remember that tuples can be passed to functions and automatically match the parameters:
func sum(x: Int, _ y: Int) -> Int {
return x + y
}
let params = (1, 1)
sum(params)
Then the behavior with ["a":1, "b":2].filter { $0.0 == "a" } can be explained by type inferring. There are two possibilities and the compiler just chose the wrong one because it thought you want to have a closure with one argument only - and that argument had to be a tuple.
You can add a comparison function to allow you to compare a Dictionary.Element and a Key
func ==<Key: Hashable,Value>(lhs:Dictionary<Key,Value>.Element, rhs:Key) -> Bool {
return lhs.0 == rhs
}
In Swift, how is tuple related to function argument?
In the following two examples the function returns the same type even though one takes a tuple while the other takes two arguments. From the caller standpoint (without peeking at the code), there is no difference whether the function takes a tuple or regular arguments.
Is function argument related to tuple in some ways?
e.g.
func testFunctionUsingTuple()->(Int, String)->Void {
func t(x:(Int, String)) {
print("\(x.0) \(x.1)")
}
return t
}
func testFuncUsingArgs()->(Int, String)->Void {
func t(x:Int, y:String) {
print("\(x) \(y)")
}
return t
}
do {
var t = testFunctionUsingTuple()
t(1, "test")
}
do {
var t = testFuncUsingArgs()
t(1, "test")
}
There is also inconsistencies in behavior when declaring tuple in function argument in a regular function (rather than a returned function):
func funcUsingTuple(x:(Int, String)) {
print("\(x.0) \(x.1)")
}
func funcUsingArgs(x:Int, _ y:String) {
print("\(x) \(y)")
}
// No longer works, need to call funcUsingTuple((1, "test")) instead
funcUsingTuple(1, "test")
funcUsingArgs(1, "test3")
UPDATED:
Chris Lattner's clarification on tuple:
"x.0” where x is a scalar value produces that scalar value, due to odd
behavior involving excessive implicit conversions between scalars and
tuples. This is a bug to be fixed.
In "let x = (y)”, x and y have the same type, because (x) is the
syntax for a parenthesis (i.e., grouping) operator, not a tuple
formation operator. There is no such thing as a single-element
unlabeled tuple value.
In "(foo: 42)” - which is most commonly seen in argument lists -
you’re producing a single element tuple with a label for the element.
The compiler is currently trying hard to eliminate them and demote
them to scalars, but does so inconsistently (which is also a bug).
That said, single-element labeled tuples are a thing.
Every function takes exactly one tuple containing the function's arguments. This includes functions with no arguments which take () - the empty tuple - as its one argument.
Here is how the Swift compiler translates various paren forms into internal representations:
() -> Void
(x) -> x
(x, ...) -> [Tuple x ...]
and, if there was a tuple? function, it would return true on: Void, X, [Tuple x ...].
And here is your proof:
let t0 : () = ()
t0.0 // error
let t1 : (Int) = (100)
t1.0 -> 100
t1.1 // error
let t2 : (Int, Int) = (100, 200)
t2.0 -> 100
t2.1 -> 200
t2.2 // error
[Boldly stated w/o a Swift interpreter accessible at the moment.]
From AirSpeedVelocity
But wait, you ask, what if I pass something other than a tuple in?
Well, I answer (in a deeply philosophical tone), what really is a
variable if not a tuple of one element? Every variable in Swift is a
1-tuple. In fact, every non-tuple variable has a .0 property that is
the value of that variable.4 Open up a playground and try it. So if
you pass in a non-tuple variable into TupleCollectionView, it’s legit
for it to act like a collection of one. If you’re unconvinced, read
that justification again in the voice of someone who sounds really
confident.
Remember the 'philosophical tone' as we've reached the 'I say potato; your say potato' phase.
A function in Swift takes a tuple as parameter, which can contain zero or more values. A parameterless function takes a tuple with no value, a function with one parameter takes a tuple with 1 value, etc.
You can invoke a function by passing parameters individually, or by grouping them into an immutable tuple. For example, all these invocations are equivalent:
do {
let t1 = testFunctionUsingTuple()
let t2 = testFuncUsingArgs()
let params = (1, "tuple test")
t1(params)
t1(2, "test")
t2(params)
t2(3, "test")
}
As an exercise, I'm trying to extend Array in Swift to add a sum() member function. This should be type safe in a way that I want a call to sum() to compile only if the array holds elements that can be added up.
I tried a few variants of something like this:
extension Array {
func sum<U : _IntegerArithmeticType where U == T>() -> Int {
var acc = 0
for elem in self {
acc += elem as Int
}
return acc
}
}
The idea was to say, “OK, this is a generic function, the generic type must be something like an Int, and must also be the same as T, the type of the elements of the array”. But the compiler complains: “Same-type requirement make generic parameters U and T equivalent”. That's right, and they should be, with the additional contraint T : _IntegerArithmeticType.
Why isn't the compiler letting me do this? How can I do it?
(I know that I should later fix how things are added up and what the return type exactly is, but I'm stuck at the type constraint for now.)
As per Martin R's comment, this is not currently possible. The thing I'm tempted to use in this particular situation would be an explicit passing of a T -> Int conversion function:
extension Array {
func sum(toInt: T -> Int?) -> Int {
var acc = 0
for elem in self {
if let i = toInt(elem) {
acc += i
}
}
return acc
}
}
Then I can write stuff like this:
func itself<T>(t: T) -> T {
return t
}
let ss = ["1", "2", "3", "4", "five"].sum { $0.toInt() }
let si = [1, 2, 3, 4].sum(itself)
An explicit function has to be passed, though. The (itself) part can of course be replaced by { $0 }. (Others have called the itself function identity.)
Note that an A -> B function can be passed when A -> B? is needed.