How to use Strings as Printables in Swift - swift

I'm trying to write a function that takes a parameter of type Printable:
func logMessage(message: Printable) {
// ...
}
Strangely, this doesn't work as expected when passing in Strings.
This doesn't compile:
logMessage("some string \(someVariable)")
// Neither does this:
let aString = "aString"
logMessage(aString)
This however compiles:
logMessage("A string")
// This works too:
let aString: Printable = "a string"
logMessage(aString)
This is quite confusing. It seems that in some cases String implements Printable and in others not.
In addition, it seems that string interpolation always produces a String that does not implement Printable. This crashes at runtime with a cast error:
let aString = "a string"
let interpolatedString = "contains \(aString)"
Any idea what's going on here?

You're right that String doesn't conform to Printable. The reason this compiles:
let aString: Printable = "Ceci n'est pas une String"
is that you aren't creating a String with that literal – you're creating an NSString (which is Printable).
Generally, in Swift, it’s usually better to write generic functions constrained by protocols. So instead of
func logMessage(message: Printable) {
// ...
}
you would probably be better off writing:
func logMessage<T: Printable>(message: T) {
// ...
}
This approach has a number of advantages – better type-safety and avoiding type erasure, more performant etc. You can read more about this stuff here.
But you'll still hit a problem because you can't pass in a String. You have two options here. First, just don't constraint it at all:
func logMessage<T>(message: T) {
// ...then use toString(message) to create a String if you need one,
// or use string interpolation or print()
}
This will work with String, and in fact will also work with anything that isn’t Printable as well (though you'll get quite a unhelpful output involving the mangled classname).
Or, you could use Streamable which strings do conform to:
func logMessage<T: Streamable>(message: T) {
println(message)
}
let s: String = "hello"
logMessage(s)
I think I read a while back on twitter one of the Swift team mention that the reason String doesn't conform to Printable is exactly because they didn't want people using Printable directly like this and that it’s better to always use toString or similar.

Related

What is the Different between String(describing: Int) vs String(Int)?

I need to convert an Integer value to String. I made a variable with an Integer value and then I have printed that using print. What is the different between the below approaches?
var word = "Count is "
var count = 100
print(word+String(describing: count)); // Count is 100
print(word+String(count)); // Count is 100
Your question is actually unnecessary because if all you want to do here is print, you can just do it directly:
print("Count is", count) // Count is 100
That's because print takes a variadic parameter and inserts space as the default separator.
However, let's answer the question anyway.
Answer
It's the difference between coercion and representation.
Coercion. Certain types can be changed into certain other types. You can change a Double to an Int and an Int to a Double. You can change an Int to a String and a String (maybe) to an Int. This is possible because the second type has an initializer whose parameter is the first type. This is something you would do in your actual program, like this:
let sum : Int = x + y
self.myLabel.text = "Your total is \(String(sum))"
Representation. For debugging purposes, all types can benefit from being representable as a string. Suppose you have a Person type. You can't change a Person to a String and vice versa — that makes no sense — but you would surely like to be able to print a Person to the console to see if you are getting the right results. This is possible because the Person type itself supplies a printable description. This is something you would do for debugging purposes:
let p = Person(first:"Matt", last:"Neuburg")
print("p is \(String(describing:p))")
Comments
Comment 1. This distinction is fairly new in Swift. It used to be that String(...) was used to express both notions. But the powers that be realized that that was a confusing conflation of the two distinct mechanisms. So nowadays, an attempt to write String(p) will fail, whereas earlier it would have succeeded. String has no Person initializer, so String(p) is forbidden; you now have to say explicitly that you are describing, not coercing.
Comment 2. The need to print a description is so obviously common that you do not have to pass thru String(describing:) if all you want to do is log the object itself. You can write print(p), because this is a shorthand for print(String(describing:p)). Similarly, string interpolation calls String(describing:) for you, so you can write print("p is \(p)"). And, as I said at the outset, print takes a variadic parameter and inserts space as the default separator, so you can write print("p is", p).
Reading the docs might help!
Here's an excerpt from String.init(describing:)
Use this initializer to convert an instance of any type to its
preferred representation as a String instance. The initializer creates
the string representation of instance in one of the following ways,
depending on its protocol conformance:
If instance conforms to the TextOutputStreamable protocol, the result is obtained by calling instance.write(to: s) on an empty string
s.
If instance conforms to the CustomStringConvertible protocol, the result is instance.description.
If instance conforms to the CustomDebugStringConvertible protocol, the result is instance.debugDescription.
An unspecified result is supplied automatically by the Swift standard library.
Int conforms to CustomStringConvertible, so String(describing: someInt) is the same as someInt.description.
Now let's look at String(someInt), this calls this initializer:
public init<T>(_ value: T) where T : LosslessStringConvertible
Int also conforms to LosslessStringConvertible. In fact, LosslessStringConvertible inherits from CustomStringConvertible. LosslessStringConvertible specifies only one requirement - an initializer that takes a string.
The docs for init<T>(_:) says this:
Creates an instance from the description of a given LosslessStringConvertible instance.
Even though description is not in a monospace font, I am quite sure (though not 100%) that this refers to the description property of CustomStringConvertible.
Assuming that init<T>(_:) indeed returns the description, String(describing: someInt) always returns the same value as String(someInt).
Note that this does not hold true for all types. It only holds true for types that conforms to LosslessStringConvertible but not TextOutputStreamable. If you have a type that conforms to both, results can be different:
struct A: TextOutputStreamable, LosslessStringConvertible {
init?(_ description: String) { }
init() { }
var description: String {
return "foo"
}
func write<Target>(to target: inout Target) where Target : TextOutputStream {
target.write("bar")
}
}
String(describing: A()) // bar
String(A()) // foo

How do I store a value of type Class<ClassImplementingProtocol> in a Dictionary of type [String:Class<Protocol>] in Swift?

I want to store a more specialized type in a Dictionary of type [String:SomeClass]. Here is some sample code illustrating my problem (also available to play with at https://swiftlang.ng.bluemix.net/#/repl/579756cf9966ba6275fc794a):
class Thing<T> {}
protocol Flavor {}
class Vanilla: Flavor {}
var dict = [String:Thing<Flavor>]()
dict["foo"] = Thing<Vanilla>()
It produces the error ERROR at line 9, col 28: cannot assign value of type 'Thing<Vanilla>' to type 'Thing<Any>?'.
I've tried casting Thing<Vanilla>() as Thing<Flavor> but that produces the error cannot convert value of type 'Thing<Vanilla>' to type 'Thing<Flavor>' in coercion.
I've also tried to define the Dictionary as type [String:Thing<Any>] but that doesn't change anything either.
How do I create a collection of different Things without resorting to plain [String:AnyObject]?
I should also mention that the class Thing is not defined by me (in fact it's about BoltsSwift Tasks), so the solution to create a base class of Thing without a type parameter doesn't work.
A Thing<Vanilla> is not a Thing<Flavor>. Thing is not covariant. There is no way in Swift to express that Thing is covariant. There are good reasons for this. If what you were asking for were allowed without careful rules around it, I would be allowed to write the following code:
func addElement(array: inout [Any], object: Any) {
array.append(object)
}
var intArray: [Int] = [1]
addElement(array: &intArray, object: "Stuff")
Int is a subtype of Any, so if [Int] were a subtype of [Any], I could use this function to append strings to an int array. That breaks the type system. Don't do that.
Depending on your exact situation, there are two solutions. If it is a value type, then repackage it:
let thing = Thing<Vanilla>(value: Vanilla())
dict["foo"] = Thing(value: thing.value)
If it is a reference type, box it with a type eraser. For example:
// struct unless you have to make this a class to fit into the system,
// but then it may be a bit more complicated
struct AnyThing {
let _value: () -> Flavor
var value: Flavor { return _value() }
init<T: Flavor>(thing: Thing<T>) {
_value = { return thing.value }
}
}
var dict = [String:AnyThing]()
dict["foo"] = AnyThing(thing: Thing<Vanilla>(value: Vanilla()))
The specifics of the type eraser may be different depending on your underlying type.
BTW: The diagnostics around this have gotten pretty good. If you try to call my addElement above in Xcode 9, you get this:
Cannot pass immutable value as inout argument: implicit conversion from '[Int]' to '[Any]' requires a temporary
What this is telling you is that Swift is willing to pass [Int] where you ask for [Any] as a special-case for Arrays (though this special treatment isn't extended to other generic types). But it will only allow it by making a temporary (immutable) copy of the array. (This is another example where it can be hard to reason about Swift performance. In situations that look like "casting" in other languages, Swift might make a copy. Or it might not. It's hard to be certain.)
One way to solve this is adding an initialiser to Thing and creating a Thing<Flavor> that will hold a Vanilla object.
It will look something like:
class Thing<T> {
init(thing : T) {
}
}
protocol Flavor {}
class Vanilla: Flavor {}
var dict = [String:Thing<Flavor>]()
dict["foo"] = Thing<Flavor>(thing: Vanilla())

Swift - Strings: Sequences or not?

I just want to sort this out once and for all:
In Swift. Can the string data type be treated as a sequence in the same way as, for example arrays and tuples?
func printAnySequence<T: SequenceType>(anything: T) {
for element in anything {
print("\(element),")
}
}
printAnySequence("A random string")
The code above generates a compiler error with the message: "Cannot invoke printAnySequence" with an argument list of type string.
let randomString: String = "A random string"
for char in randomString {
print(char)
}
Whereas the code above generate a compiler error with the message: "Type 'String' does not conform to the protocol 'SequenceType'"
However the documentation for Swift explicitly says that strings are fixed sequences of characters.
Where is my error?
Thanks
Max Xie
No, String does not implement SequenceType. The documentation calls a string literal a "fixed sequence of textual characters", but this doesn't have anything to do with the SequenceType protocol. String.characters, however, has type String.CharacterView which does implement SequenceType (it even implements BidirectionalCollectionType).
If you really wanted to, you could let String conform to BidirectionalCollectionType (and RangeReplaceableCollectionType) very easily:
extension String: BidirectionalCollectionType, RangeReplaceableCollectionType {
// String already implements the necessary methods
// and properties, so we can leave this empty
}
However, this is not recommended. The core Swift team left out these conformances for a reason: methods in protocol extensions on SequenceType, CollectionType and other protocols might make assumptions about the sequence/collection that might not hold for String, due to the nature of Swift strings.

Convert or cast object to string

how can i convert any object type to a string?
let single_result = results[i]
var result = ""
result = single_result.valueForKey("Level")
now i get the error: could not assign a value of type any object to a value of type string.
and if i cast it:
result = single_result.valueForKey("Level") as! String
i get the error:
Could not cast value of type '__NSCFNumber' (0x103215cf0) to 'NSString' (0x1036a68e0).
How can i solve this issue?
You can't cast any random value to a string. A force cast (as!) will fail if the object can't be cast to a string.
If you know it will always contain an NSNumber then you need to add code that converts the NSNumber to a string. This code should work:
if let result_number = single_result.valueForKey("Level") as? NSNumber
{
let result_string = "\(result_number)"
}
If the object returned for the "Level" key can be different object types then you'll need to write more flexible code to deal with those other possible types.
Swift arrays and dictionaries are normally typed, which makes this kind of thing cleaner.
I'd say that #AirSpeedVelocity's answer (European or African?) is the best. Use the built-in toString function. It sounds like it works on ANY Swift type.
EDIT:
In Swift 3, the answer appears to have changed. Now, you want to use the String initializer
init(describing:)
Or, to use the code from the question:
result = single_result.valueForKey("Level")
let resultString = String(describing: result)
Note that usually you don't want valueForKey. That is a KVO method that will only work on NSObjects. Assuming single_result is a Dictionary, you probably want this syntax instead:
result = single_result["Level"]
This is the documentation for the String initializer provided here.
let s = String(describing: <AnyObject>)
Nothing else is needed. This works for a diverse range of objects.
The toString function accepts any type and will always produce a string.
If it’s a Swift type that implements the Printable protocol, or has overridden NSObject’s description property, you’ll get whatever the .description property returns. In the case of NSNumber, you’ll get a string representation of the number.
If it hasn’t, you’ll get a fairly unhelpful string of the class name plus the memory address. But most standard classes, including NSNumber, will produce something sensible.
import Foundation
class X: NSObject {
override var description: String {
return "Blah"
}
}
let x: AnyObject = X()
toString(x) // return "Blah"
"\(x)" // does the same thing but IMO is less clear
struct S: Printable {
var description: String {
return "asdf"
}
}
// doesn't matter if it's an Any or AnyObject
let s: Any = S()
toString(s) // reuturns "asdf"
let n = NSNumber(double: 123.45)
toString(n) // returns "123.45"
n.stringValue // also works, but is specific to NSNumber
(p.s. always use toString rather than testing for Printable. For one thing, String doesn’t conform to Printable...)
toString() doesn't seem to exist in Swift 3 anymore.
Looks like there's a failable initializer that will return the passed in value's description.
init?(_ description: String)
Docs here https://developer.apple.com/reference/swift/string/1540435-init

Can you enforce type conversion in a protocol without defining a property/method?

I don't want to have to define asString.
protocol ConvertibleToString {var asString: String {get}}
extension Int: ConvertibleToString {
var asString: String {return String(self)}
}
If I understand your question correctly – no, I don't think you can't define an "implicit" conversion that detects and uses a matching init from a specific type. The only way to convert from one type to another in Swift is to explicitly call an init for the "to" type that takes the "from" type, or a function or method on the "from" type that returns the "to" type. There's no way of implementing a protocol that says "use the init for this type with other type, if one is available".
By the way, your ConvertibleToString protocol is essentially a version of Printable (with asString in place of description). So if what you want is to know if something is convertible to a string, you can just check for conformance to Printable. Though note one gotcha – String is not Printable. You can use toString(thing) to convert anything to a string, and it will use Printable where available (and do nothing to convert strings), though this does have the side-effect of giving you a default for non-printable types that you may not want depending on your need.
Note you can require convertibility from something via a protocol:
protocol ConvertibleFromInt {
init(Int)
}
extension String: ConvertibleFromInt { }
extension UInt64: ConvertibleFromInt { }
func gimmeFromInt<T: ConvertibleFromInt>(i: Int) -> T {
return T(i)
}
let s: String = gimmeFromInt(5)
let ui: UInt64 = gimmeFromInt(5)