Swift Implement Literalconvertible Protocol

Literal Convertibles in Swift

  1. You are partially correct in your understanding of the various ExpressibleBy...Literal protocols. When the Swift compiler parses your source code into an Abstract Syntax Tree, it already identified what literal represents what data type: 5 is a literal of type Int, ["name": "John"] is a literal of type Dictionary, etc. Apple makes the base type conform to these protocols for the sake of completeness.
  2. You can adopt these protocols to give your class an opportunity to be initialized from a compile-time constant. But the use case is pretty narrow and I don't see how it applies to your particular situation.

For example, if you want to make your class conform to ExpressibleByStringLiteral, add an initializer to set all your properties from a String:

class Employee: ExpressibleByStringLiteral {
typealias StringLiteralType = String

var name: String
var salary: Int

required init(stringLiteral value: StringLiteralType) {
let components = value.components(separatedBy: "|")
self.name = components[0]
self.salary = Int(components[1])!
}
}

Then you can init your class like this:

let employee1: Employee = "John Smith|50000"

But if you dream about about writing something like this, it's not allowed:

let str = "Jane Doe|60000"
let employee2: Employee = str // error

And if you pass in the wrong data type for salary, it will be a run time error instead of a compile-time error:

let employee3: Employee = "Michael Davis|x" // you won't know this until you run the app

TL, DR: it is a very bad idea to abuse these ExpressibleBy...Literal types.

Swift LiteralConvertible not inferred in return type

After some discussion in the comments above, I realise what you want to ask is:

  • Given the context of my struct including its extension, why does the randomTemperature() -> Temperature method fail when I try to to send an int value property as return type?

In code:

func randomTemperature() -> Temperature {
let anyInt = 1
return anyInt
// Error: "Cannot convert return expression of
// type 'Int' to return type 'Temperature'
}

The short answer is: you cannot convert type 'Int' to return type Temperature' (as error states). What about your initialiser, you ask? The Temperature: IntegerLiteralConvertible init function init(integerLiteral value: IntegerLiteralType) only works on literals (IntegerLiteralType), and an integer is not a literal.

For a more thorough answer, see below.


From Apple`s documentation on literals, it says

A literal is the source code representation of a value of a type, such
as a number or string.

...

A literal doesn’t have a type on its own. Instead, a literal is parsed as having infinite precision and Swift’s type inference
attempts to infer a type for the literal.

...

When specifying the type annotation for a literal value, the
annotation’s type must be a type that can be instantiated from that
literal value
. That is, the type must conform to one of the
following Swift standard library protocols:
IntegerLiteralConvertible for integer literals ...

And from Apple's documentation on the IntegerLiteralConvertible protocol:

Conforming types can be initialized with integer literals.

Ok, so literal never have a type of their own. This sorts things out.

Consider, along the Temperature struct, the Double type, a type which also conforms to IntegerLiteralConvertible.

For Double, we have:

func someDouble() -> Double {
return 1 // This is ok, "1" here is a literal, and the return
// of type Double can be initialised via the literal
}

func someDoubleTrouble() -> Double {
let anyInt = 1
return anyInt // Error! anyInt is not a literal, but an Int type,
// and return typ expects Double (or literal).
}

// The same applies for these
var someInt = 1
var anotherDouble: Double = 1 // ok, as above, literal
var anotherDoubleTrouble: Double = someInt // Error!

Exactly the same applies for your Temperature struct type.

func someTemperature() -> Temperature {
return 1 // Ok
}

func someTroubledTemperature() -> Temperature {
let myInt = 1
return myInt // Error!
}

// The same applies for these
var someInt = 1
var anotherTemperature: Temperature = 1 // ok, as above, literal
var anotherTroubledTemperature: Temperature = someInt // Error!

Swift type does not conform to protocol NilLiteralConvertible

If your function can return nil, then it should be declared as ... -> MeeterAccount? (an optional type). Then the caller knows that it can be nil. (Your second example works but would crash because return out will implicitly try to unwrap nil.)

Swift protocol and return types on global functions

This question has the same form as the copy one, and the same solution. Make mutation an initializer rather than a method.

protocol Copyable {
init(copy: Self)
}

protocol Mutatable : Copyable {
init(byMutating: Self)
}

class C : Mutatable {
var a = 0

required init(_ a: Int) {
self.a = a
}

required init(copy: C) {
a = copy.a
}

required convenience init(byMutating: C) {
self.init(copy: byMutating)
self.a++
}
}

// These are purely for convenience
func copy<T : Copyable>(x: T) -> T {
return x.dynamicType(copy: x)
}

func mutated<T: Mutatable>(x: T) -> T {
return x.dynamicType(byMutating: x)
}

But to reiterate Mattt's point in the linked article, you can have a C(copy: x) syntax fairly conveniently, and you can have a copy(x) syntax pretty conveniently, and there is always x.dynamicType(copy: x). But you can't have a x.copy() syntax without some annoying work. You either have to duplicate func copy() -> Self { return copy(self) } in every class, or you have to create some concrete class that implements this method and C ultimately inherits from. This is currently a basic limitation of Swift. I agree with Mattt's diagnosis of possible solutions, and suspect that some kind of trait system, possibly along the lines of Scala's, will probably be added in the future.

It's worth focusing on Mattt's comment that "all of this highlights a significant tension between methods and functions in Swift." This is another way of saying that there are tensions between the object-oriented paradigm and the functional paradigm, and moving between them can create some disconnects. Languages try to paper-over that issue with various features, but there are important differences between objects with messages and properties, vs functions with data and combinators, and "getting the best of both worlds" can sometimes create some rough edges.

It's easy to forget, when comparing Swift to other languages, that there is a big difference between v0.9 and v2.11. Many things we take for granted in our favorite languages did not exist in their v1 either.


To your comment, you may be thinking that mutated is of type Self. But it's of type C, as your autocomplete indicates. As before, C is not the same as Self unless you can promise that there are no subclasses (C being either final or a struct). Swift types are resolved at compile-time, not runtime, unless you use dynamicType.

To be a little more specific, Swift looks at this line:

    let mutated = copy(self)

It notes that copy is generic on the type of its parameter, and it must construct a version of copy at compile-time to call. There is no type Self. It's just a placeholder, and must be resolved at compile-time. The type of self in this lexical scope is C. So it constructs copy<C>. But if you subclassed C, this could be the wrong function (and in this case, would be). This is very closely related to: https://stackoverflow.com/a/25549841/97337.

The fact that type autocomplete says (C) rather than C is a minor side-effect of how Swift functions and tuples work, and comes up pretty regularly, but I've yet to encounter a case where it really mattered. A Swift function like func f(x: Int, y:Int) does not actually have two parameters. It has one 2-tuple parameter of type (Int, Int). This fact is important to how the currying syntax works (see the Swift Programming Language for more on currying in Swift). So when you specialize copy, you specialized it with a 1-tuple of type (C). (Or possibly, the compiler is just trying to do that as one of various attempts, and that's just the one it reports on.) In Swift any value can be trivially exchanged for a 1-tuple of the same type. So the return of copy is actually the 1-tuple of C, written (C). I suspect that the Swift compiler will improve its messages over time to remove the extraneous parentheses, but that's why they show up sometimes.

Swift, implement same delegate two times (without multicast)

It is actually possible using a Proxy delegate. However not really recommended.

In Swift, how do I have a UIScrollView subclass that has an internal and external delegate?

Swift - is there a boolean convertible protocol or any other way to overload casting to a custom type?

There were features to support things like this in early versions of Swift and they were intentionally removed. Swift typically avoids implicit type conversions. They tend to explode compile times, and often lead to unexpected and undesired conversions. The current implicit conversion from T to T? is a common source of confusing compiler errors and incorrect overload calls in generic code. Building more things like that without many more changes to the Swift compiler is problematic. Even implicit numeric conversions, which the Swift team has expressed as desirable in principle, are not currently possible due to these problems and you need to explicitly convert.

The preferred way is to define an explicit overload in cases where it is useful:

func def(a: Bool) { def(CustomType(a)) }


Related Topics



Leave a reply



Submit