Why aren't Range and Array LosslessStringConvertible?

Are there any reasons why eg Array, Range and ClosedRange shouldn't conform to LosslessStringConvertible?

Naive implementation

extension Range: LosslessStringConvertible where
Bound: LosslessStringConvertible
{
public init?(_ description: String) {
let c = description.components(separatedBy: "..<")
guard c.count == 2, let lower = Bound(c[0]), let upper = Bound(c[1])
else { return nil }
self = lower ..< upper
}
}
extension ClosedRange: LosslessStringConvertible where
Bound: LosslessStringConvertible
{
public init?(_ description: String) {
let c = description.components(separatedBy: "...")
guard c.count == 2, let lower = Bound(c[0]), let upper = Bound(c[1])
else { return nil }
self = lower ... upper
}
}
extension Array: LosslessStringConvertible where
Element: LosslessStringConvertible
{
public init?(_ description: String) {
self.init()
for oe in description
.dropFirst().dropLast().components(separatedBy: ", ")
{
guard let e = Element(oe) else { return nil }
self.append(e)
}
}
}

The issue here is that lossless strings aren’t a text safe encoding - E.g code trying to parse an array of strings wouldn’t know if a comma was emitted by it or one of its Elements

6 Likes

Right, there is no limit on what the lossless string to which a value is converted may contain, so no way to ensure that any delimiter (e.g., commas, "...", etc.) doesn't appear in the lossless string representation of the bound of a range or an element of an array in a way that is distinguishable from the delimiter. It is perfectly valid for me to create a custom type where the lossless string representation of a value is ,,,....

3 Likes

Good question. It is strange, because Array<T: Decodable> can be decoded. For example, Array can be decoded from json string "[1, 2, 3]", So, for now it is possible to init an Array from String, though making it using JsonDecoder. Seems that Array<T: LosslessStringConvertible> should also be LosslessStringConvertible by itself.

Of course we can't simply join the lossless string representations of the elements (in the case of Array) with a separator like ,. But we should be able to escape the lossless string representations of the elements, for example by replacing occurrences of \ with \\ and , with \,, and then join those.

Wouldn't that require changing the .description / CustomStringConvertible-conformance of Array or its Element (and [Closed]Range or its Bound)?

I mean, since:

public protocol LosslessStringConvertible : CustomStringConvertible {

    /// Instantiates an instance of the conforming type from a string
    /// representation.
    init?(_ description: String)
}

you would have to change it in any case, because currently Array uses debugDescription of the elements, not description

example with your naive implementation
import Foundation

extension Array: LosslessStringConvertible where
    Element: LosslessStringConvertible
{
    public init?(_ description: String) {
        self.init()
        for oe in description
            .dropFirst().dropLast().components(separatedBy: ", ")
        {
            guard let e = Element(oe) else { return nil }
            self.append(e)
        }
    }
}
struct Foo: CustomDebugStringConvertible, LosslessStringConvertible {
    init?(_ description: String) {
        guard description == "B" else { return nil }
    }
    init() { }
    var debugDescription: String { "A" }
    var description: String { "B" }
}
let arr = [Foo()]
print([Foo](arr.description)) // nil
1 Like

Sure, I accepted the explanations of why it wouldn't work, ie that the following is the answer to the question of this thread:

My bad, I thought LosslessStringConvertible consisted of an additional string property that we could use without changing the semantics of description and debugDescription.

I wonder why it doesn't / if that wouldn't be better. Ie, how does the benefits of letting LosslessStringConvertible inherit from CustomStringConvertible outweigh any potential confusion / trouble / limitations caused by that entanglement?

Seems to me like LosslessStringConvertible is more "critical" than CustomStringConvertible, yet the former is based on the latter.

CustomStringConvertible's documentation even states that:

Accessing a type’s description property directly or using CustomStringConvertible as a generic constraint is discouraged.