Can anyone explain what stringConverter as (String) -> String in combination with things.append({ (name: String) -> String in "Hello, \(name)" }) actually do?
(Source: https://docs.swift.org/swift-book/LanguageGuide/TypeCasting.html)
I think an anonymous function is added to the collection and later gives it a name in the switch statement to work with it...
things is an Array that has no constraint on what it can contain...
var things: [Any] = []
And it contains all sorts of different types...
things.append(0) // Int
things.append(0.0) // Double
things.append(42)
things.append(3.14159)
things.append("hello") // String
things.append((3.0, 5.0)) // (Double, Double) tuple
things.append(Movie(name: "Ghostbusters", director: "Ivan Reitman")) // Movie struct
things.append({ (name: String) -> String in "Hello, \(name)" })
The last line above adds a function that takes a String and returns a String. (String) -> String.
When iterating over this array we have to determine what each object is as they could be anything.
The pattern matching of switch and case allows us to do this...
for thing in things {
switch thing {
case 0 as Int:
print("zero as an Int")
case 0 as Double:
print("zero as a Double")
case let someInt as Int:
print("an integer value of \(someInt)")
case let someDouble as Double where someDouble > 0:
print("a positive double value of \(someDouble)")
case is Double:
print("some other double value that I don't want to print")
case let someString as String:
print("a string value of \"\(someString)\"")
case let (x, y) as (Double, Double):
print("an (x, y) point at \(x), \(y)")
case let movie as Movie:
print("a movie called \(movie.name), dir. \(movie.director)")
case let stringConverter as (String) -> String:
print(stringConverter("Michael"))
default:
print("something else")
}
}
What this bit does...
case let stringConverter as (String) -> String:
print(stringConverter("Michael"))
Is saying... "create a new variable from the thing called stringConverter and cast it to a (String) -> String type. If that works then we pattern match on it and print the result of the function".
It's not actually changing the type of the thing it's just using the ability to cast it as a pattern matching mechanism to satisfy the switch/case.
Related
I have an Enum with 2 cases and each take a String and an Int as properties:
public enum TestEnum {
case case1(String, Int? = nil)
case case2(String, Int? = nil)
}
I create an enum with value case1 and these 2 properties:
let e = TestEnum.case1("abc", 123)
my question is how can I get
I tried
let a = e.case1.0 // expect to get 'abc' back
let b = e.case1.1 // expect to get '123' back
print ("\(a)")
print ("\(b)")
But I get compile error 'Enum case 'case1' cannot be used as an instance member'
The type of the variables is TestEnum for both a and b. Which exact case the variable represents isn't encoded in the type. Because of this, you cannot access associated values of an enum case variable.
Instead, you need to use if case let to conditionally cast the enum case and assign its associated values to variables.
let a = TestEnum.case1("a", 1)
if case let .case1(string, int) = a {
print(string, int)
}
You can use pattern matching to access the values. (see patterns documentation)
Using switch:
switch e {
case .case1(let stringValue, let intValue):
print("stringValue: \(stringValue), intValue: \(intValue)")
default:
break
}
or if:
if case .case1(let stringValue, let intValue) = e {
print("stringValue: \(stringValue), intValue: \(intValue)")
}
The code below compiles and runs OK, and seems to indicate that the closure and String.init(describing:) functions are completely equivalent in their signature, since .map method happily takes both of them.
let someDict: [String: String] = [
"string1" : "Hello",
"string2" : "Bye",
]
//One way to call .map
var closure = { (key: String, value: String) -> String in
return "The key is \(key), the value is \(value)"
}
someDict.map(closure)
//Another way to call .map
someDict.map(String.init(describing:))
But how is it possible to place into .map a String.init(describing:) function which is a function of only 1 argument, while .map expects a function of 2 arguments? Or am i misunderstanding something here..
Btw, checking the documentation shows that it really does expect a function of 2 arguments:
transform: ((key: String, value: String)) throws -> T
Btw, checking the documentation shows that it really does expect a
function of 2 arguments:
transform: ((key: String, value: String)) throws -> T
Actually, no. Notice the extra parentheses (). It shows that it expects a function that takes one argument which is a tuple containing two elements.
Consider this example:
// function foo takes two arguments
func foo(_ a: Int, _ b: Int) -> Int {
return a + b
}
// function bar takes one tuple with two elements
func bar(_ a: (Int, Int)) -> Int {
return a.0 + a.1
}
let f1 = foo
print(type(of: f1)) // (Int, Int) -> Int
let f2 = bar
print(type(of: f2)) // ((Int, Int)) -> Int
So, the extra parentheses tell us that map is expecting one argument that is a tuple containing two elements.
The closure passed to map always operates on a single element from the sequence at a time. That element can be a tuple such as your case, and then your closure can deconstruct that tuple into multiple values.
Consider this example:
// tup is a tuple containing 3 values
let tup = (1, true, "hello")
// deconstruct the tuple through assignment
let (x, y, z) = tup
print(x) // 1
print(y) // true
print(z) // hello
So in this example:
var closure = { (key: String, value: String) -> String in
return "The key is \(key), the value is \(value)"
}
someDict.map(closure)
map's closure is given a tuple of the form (key: String, value: String) and the closure is deconstructing that into key and value just as the let did above.
In this example:
someDict.map(String.init(describing:))
which is equivalent to:
someDict.map({ String(describing: $0) })
map is taking the whole tuple and passing it to String(describing:).
suppose I have
enum Example {
case one(string: String)
case two(string: String)
}
and now I have
let x = Example.one(string: "Hello")
The question:
let y = ?
how do I create another instance of the same enum in e, so that I end up with y == .one("World"))
The types of enum cases with associated values are closures with arguments corresponding to the type of the associated values, and with a return corresponding to the type of the enum (with the value of the return being the specific case). I.e., for your example above, the type of Example.one as well as Example.two is (String) -> Example, where the closures expressed by these two cases yield different results; instances of .one(...) and .two(...), respectively.
Hence, instead of writing your own method to "clone" a given case, you could simply have a computed property which returns the already existing closures Example.one and Example.two (if self is one or two, respectively), which can subsequently be invoked upon a String argument to construct a new Example instance (with value .one or .two; along with the supplied associated String value).
E.g.:
enum Example {
case one(string: String) // type: (String) -> Example
case two(string: String) // type: (String) -> Example
var caseClosure: (String) -> Example {
switch self {
case .one: return Example.one
case .two: return Example.two
}
}
}
let x = Example.one(string: "Hello") // .one("Hello")
let y = x.caseClosure("World") // .one("World")
However, since all the cases in your example are closures of the same type, namely (String) -> Example (i.e. have the same number and type(s) of associated values), you might as well, as already proposed in a comment by #Hamish, wrap an enum with no associated values in a struct along with the always-String "associated value" a separate member of the struct. E.g. expanding Hamish's example with some initializers:
struct S {
enum E {
case one
case two
}
var e: E
var string: String // Since "associated value" is always the same type
init(_ e: E, string: String) {
self.e = e
self.string = string
}
init(from s: S, string: String) {
self.e = s.e
self.string = string
}
}
let x = S(.one, string: "Hello")
let y = S(from: x, string: "World")
let z = S(x.e, string: "World")
You do that by calling the Initializer exactly like you did for x:
enum Example {
case one(string: String)
case two(string: String)
}
let x = Example.one(string: "Hello")
print(x) // Prints one("Hello")
let y = Example.one(string: "World")
print(y) // Prints one("World")
Also, The , in your enum declaration is wrong and has to be removed.
UPDATE:
The comment explained the question in more detail, so here is my updated answer:
An elegant way to solve this is to use a function on the original enum type Example.
enum Example {
case one(string: String)
case two(string: String)
func cloneWith(string: String) -> Example {
switch self {
case .one:
return .one(string: string)
case .two:
return .two(string: string)
}
}
}
let x = Example.one(string: "Hello")
print(x) // Prints one("Hello")
let y = x.cloneWith(string: "World")
print(y) // Prints one("World")
I set the constant calculationError inside the structure PendingBinaryOperation
I create a variable pendingBinaryOperation of type PendingBinaryOperation
I get the error in a case statement that calculationError is an unresolved identifier. The section of the code is below.
mutating func performOperation(_ symbol:String){
if let currentOperation = operations[symbol] {
switch currentOperation {
case .nullOperation(let function, let nullDiscription):
accumulator = (function(), nullDiscription, nil)
case .constant(let value):
accumulator = (value,symbol, nil)
case .unaryOperation(let function, let unaryDescription, let unaryMemory):
if accumulator != nil {
accumulator = (function(accumulator!.digit), unaryDescription(accumulator!.description),
unaryMemory(accumulator!.digit))
}
case .binaryOperation(let binaryFunction,
let binaryDescription,
let binaryError):
if accumulator != nil {
doAPendingBinaryOperation()
resultIsPending = true
pendingBinaryOperation = PendingBinaryOperation(
mathFunction: binaryFunction,
calculationDescription:binaryDescription,
firstOperand: accumulator!.digit,
descriptionOperand:(accumulator?.description)!,
THIS LINE GIVES THE ERROR ->binaryError: calculationError)
accumulator = nil
}
case .equals:
doAPendingBinaryOperation()
resultIsPending = false
}
}
}
private var pendingBinaryOperation:PendingBinaryOperation?
struct PendingBinaryOperation {
let calculationError: (Double, Double) -> String
let mathFunction: (Double, Double) -> Double
let calculationDescription: (String, String) -> String
let firstOperand: (Double)
let descriptionOperand:(String)
func perform(with secondOperand:(Double)) -> Double {
return (mathFunction(firstOperand, secondOperand))
}
func performDecription(with secondOperan:(String)) -> String {
return (calculationDescription(descriptionOperand, secondOperan))
}
}
Based on the code you show, the constructor for PendingBinaryOperation is:
PendingBinaryOperation(calculationError: (Double, Double) -> String, mathFunction: (Double, Double) -> Double, calculationDescription: (String, String) -> String, firstOperand: (Double), descriptionOperand: (String))
but you are calling:
PendingBinaryOperation(mathFunction: (Double, Double) -> Double, calculationDescription: (String, String) -> String, firstOperand: (Double), descriptionOperand:(String), binaryError: ??)
As you can see, the order of your parameters are all wrong, and you have a parameter called binaryError that doesn't exist in the type.
I'm guessing you need to reverse binaryError: calculationError to calculationError: binaryError and re-order your parameters so they are correct.
Structs in Swift automatically generate a memberwise initializer, that's what you're using here to initialize PendingBinaryOperation. This requires that all properties be set in the initializer, including binaryError.
You're trying to pass calculationError as the value of the binaryError argument - this is not possible as calculationError is unavailable in this scope (and has no value).
If you don't want to set binaryError, you'll have to abandon using the memberwise initializer. This usually means giving add of PendingBinaryOperation properties an initial value, and using the normal init (i.e. PendingBinaryOperation()).
Why is the type cast operator (as) being used instead of its conditional form (as?) in this switch statement?
I thought the type operator could only be (as?) or (as!)...? The Apple Swift documentation does not provide adequate explanation about this.
Here is the example in the Swift documentation:
var things = [Any]()
things.append(0)
things.append(0.0)
things.append(42)
things.append(3.14159)
things.append("hello")
things.append((3.0, 5.0))
things.append(Movie(name: "Ghostbusters", director: "Ivan Reitman"))
things.append({ (name: String) -> String in "Hello, \(name)" })
for thing in things {
switch thing {
case 0 as Int:
println("zero as an Int")
case 0 as Double:
println("zero as a Double")
case let someInt as Int:
println("an integer value of \(someInt)")
case let someDouble as Double where someDouble > 0:
println("a positive double value of \(someDouble)")
case is Double:
println("some other double value that I don't want to print")
case let someString as String:
println("a string value of \"\(someString)\"")
case let (x, y) as (Double, Double):
println("an (x, y) point at \(x), \(y)")
case let movie as Movie:
println("a movie called '\(movie.name)', dir. \(movie.director)")
case let stringConverter as String -> String:
println(stringConverter("Michael"))
default:
println("something else")
}
}
Here is the link to the Apple Swift documentation on Type Casting
You could have found the answer yourself if you read the note on the bottom:
The cases of a switch statement use the forced version of the type cast operator (as, not as?) to check and cast to a specific type. This check is always safe within the context of a switch case statement.
(emphasis mine)
Here is an Apple blog post which elaborates on the difference between as?, as and as!.
The as in case 0 as Int: or case let someInt as Int:
is part of
a type casting pattern. In the Swift Language Reference the case label of a switch statement
is defined as
case-label → case case-item-list:
case-item-list →
pattern guard-clauseopt | pattern guard-clauseopt , case-item-list
where pattern can be (among other cases)
pattern → value-binding-pattern
pattern → type-casting-pattern
pattern → expression-pattern
and a type casting pattern is
type-casting-pattern → is-pattern | as-pattern
is-pattern → is type
as-pattern → pattern as type
So you have for example
case let someInt as Int:
╰──────────────────────╯ case-label
╰────────────────╯ case-item-list -> type-casting pattern
╰─╯ type
╰╯ `as` keyword
╰─────────╯ value-binding pattern
Swift-Blog:
It may be easiest to remember the pattern for these operators in Swift as: ! implies “this might trap,” while ? indicates “this might be nil.”
And there is the third possibility of a guaranteed conversion, when an upcast is done. For example 2 as Any gets a warning when used with as! or as?
In case of the switch construct, the case let value as Type: never fails nor is there the possibility, that value will be an optional type in contrast to the expression value as? Type