This question is just having fun with Swift. Please don't take it too seriously...
In Swift, given this function:
func greet(name: String) {
print("Hello \(name)!")
}
the proper way of calling it is like this:
greet(name: "Bob") // "Hello Bob!"
If you omit the argument label name:
greet("Bob")
you get the error: Missing argument label 'name:' in call
But treating the function as a closure allows you to call it without the argument label:
let g = greet; g("Bob") // "Hello Bob!"
This made me wonder, what is the most concise way of calling a function without using its argument label?
Wrap it in a tuple?
I thought wrapping the function in a tuple and then extracting it would work:
(greet).0("Bob")
but this resulted in the error: Value of type '(String) -> ()' has no member '0'.
This works:
(greet,greet).0("Bob") // "Hello Bob!"
So apparently, Swift treats simple tuples of 1 value as just the value itself.
Giving the tuple argument a label works:
(g:greet).g("Bob") // "Hello Bob!"
(g:greet).0("Bob") // "Hello Bob!"
Put it in an array?
So far, the most concise way I have found is to put the function into an array:
[greet][0]("Bob") // "Hello Bob!"
This uses only 5 characters more than greet("Bob") which would seem to be the shortest possible call if it worked.
Is there any shorter way to call the greet(name:) function without using the argument label than this?
Related
I was trying to define a variable that can hold a closure and ran into some difficulties using Swift's shorthand argument names. Take the following code snippet:
var returnAString: (String, String) -> String
returnAString = { return $0 }
This gives me the compile error '(String, String)' is not convertible to 'String'. When I modify the closure return a string with the first argument value, I get back both argument values in a tuple.
println(returnAString("1", "2")) // Prints "(1, 2)"
However, if I modify the closure to print the second argument the correct argument is printed as expected.
returnAString = { $1 }
println(returnAString("1", "2")) // Prints "2"
I've got the feeling that I'm doing something a little silly here, but I couldn't find any explanation of why this would be happening.
Closures § Shorthand Argument Names
Shorthand Argument Names
…
the number and type of the shorthand argument names will be inferred from the expected function type.
It looks like this is the expected behavior. By providing only $0 you are inferring (at least to the system) that you want a tuple.
This only seems to be a special case of using $0. For example, the following cannot be compiled.
var returnAString3: (String, String, String) -> String
returnAString3 = { return $1 }
No matter how it's modified.
returnAString3 = { return $1.0 } // still fails.
Type inferral is treating $0 as a tuple instead of the first argument, because you are not using the 2nd parameter.
If you just reference the 2nd parameter instead it works correctly:
returnAString = { $1; return $0 }
A proof that what I am saying is correct comes from 0x7fffffff's comment:
returnAString = { $0.0 }
which means take the first value of the $0 tuple and return it.
To the language, "multiple parameters" is the same as a single parameter that is a tuple. Since only $0 was used, and not $1, etc., the compiler first guesses that there is only one parameter. And that guess is always valid because any function type can be viewed to have exactly one parameter (which may be Void, a tuple, or otherwise).
I have a Swift 3 array that's initialized with the following:
var foo: [String] = []
In one of my methods that gets invoked recursively, I'm attempting to convert a string into an array of characters, but append those characters versus doing a direct assignment to foo. So this will compile:
self.foo = text.characters.map { String($0) }
But the following breaks:
self.foo.append(text.characters.map { String($0) })
The error it produces is: 'map' produces '[T]', not expected contextual result type 'String'
What's the correct way to approach this?
You need to be using the append(contentsOf:) method instead.
foo.append(contentsOf: text.characters.map { String($0) })
This method can take an array of the defined type.
Whereas the append() method expects a single element to add at the end of the array.
In Swift it seems to be possible to create a variadic function with a vararg parameter which isn't the last parameter of the function.
I created the following function:
func test1(_ array: String..., _ tag: String){
print("[\(tag)] array: \(array)")
}
Because it's possible to define a vararg parameter without making it the last parameter I suspected that the Swift compiler would just take the last argument and make it the argument for the second parameter.
test1("Hello", "World", "Greeting")
But this doesn't work, which is not too strange.
This doesn't work either:
let arrayOfStrings: [String] = ["Hello", "World"]
test1(arrayOfStrings, "Greeting")
Obviously giving the second parameter a label works.
func test2(_ array: String..., tag: String){
print("[\(tag)] array: \(array)")
}
But I decided to make another try. This function compiles doesn't generate a warning, but can I even use it?
func test3(_ array: String..., _ tag: Int){
print("[\(tag)] array: \(array)")
}
test3("Hello", "World", 1)
Is it possible to call the first method or the third method, without giving the second parameter a label?
Side note: Is this a language feature? Shouldn't the compiler have warn me? It currently tells me that I'm missing the second parameter when calling the function, but to me it seems that it's not even possible to provide this parameter.
I am new in swift . let' try to understand closures
declare a function like closures:
let sayHello = {
return "hello"
}
unable to infer closures return type of current context ()->()
My question is here what is the context ?
let sayHello : String = {
return "hello"
}
Function produce expected type of "string" , did you mean to call it with ()
honestly i did not understand this error ? anyone help me to understand and why need to specify this one ()
however . this is working fine and without error it is called closures
let sayHello : String = {
return "hello"
}()
thank you
The context the error message refers to means the initialization statement for the closure, along with its surrounding.
Swift can often infer (figure out from things that it already knows) the type of closure, without requiring you to specify it explicitly.
For example, if you are calling a function that takes a closure taking an Int as its argument, Swift figures out that the type of your closure's argument must be an Int. Similarly, if your closure returns a captured local variable of type String, Swift does not need you to specify the return type, because it figures it must be String as well.
None of this works when you create closures for future use, and assign them to a variable. In situations like that you need to tell Swift what kind of closure you are creating:
let sayHello : ()->String = {
return "hello"
}
Now you have a variable sayHello of type "a closure that takes no arguments and returns a String" (i.e. ()->String closure). You can call it later to say hello:
let hello = sayHello()
Note: Your example that "worked" also defined a closure. However, the closure was used immediately after its definition, and then discarded.
TL;DR
Why doesn't this work?
"abcdefg".characters.map(String.init) // error: type of expression is ambiguous without more context
Details
One really cool thing I like in Swift is the ability to convert a collection of one thing to another by passing in an init method (assuming an init() for that type exists).
Here's an example converting a list of tuples to instances of ClosedInterval.
[(1,3), (3,4), (4,5)].map(ClosedInterval.init)
That example also takes advantage of the fact that we can pass a tuple of arguments as a single argument as long as the tuple matches the function's argument list.
Here another example, this time converting a list of numbers to string instances.
(1...100).map(String.init)
Unfortunately, the next example does not work. Here I am trying to split up a string into a list of single-character strings.
"abcdefg".characters.map(String.init) // error: type of expression is ambiguous without more context
map() should be operating on a list of Character (and indeed I was able to verify in a playground that Swift infers the correct type of [Character] here being passed into map).
String definitely can be instantiated from a Character.
let a: Character = "a"
String(a) // this works
And interestingly, this works if the characters are each in their own array.
"abcdefg".characters.map { [$0] }.map(String.init)
Or the equivalent:
let cx2: [[Character]] = [["a"], ["b"], ["c"], ["d"]]
cx2.map(String.init)
I know that I could do this:
"abcdefg".characters.map { String($0) }
But I am specifically trying to understand why "abcdefg".characters.map(String.init) does not work (IMO this syntax is also more readable and elegant)
Simplified repro:
String.init as Character -> String
// error: type of expression is ambiguous without more context
This is because String has two initializers that accept one Character:
init(_ c: Character)
init(stringInterpolationSegment expr: Character)
As far as I know, there is no way to disambiguate them when using the initializer as a value.
As for (1...100).map(String.init), String.init is referred as Int -> String. Although there are two initializers that accept one Int:
init(stringInterpolationSegment expr: Int)
init<T : _SignedIntegerType>(_ v: T)
Generic type is weaker than explicit type. So the compiler choose stringInterpolationSegment: one in this case. You can confirm that by command + click on .init.