0

Is there a way in Swift to define an extension for type Double to accept String as initializer? In a nutshell, just to figure out feasibility, I need this to work:

var double:Double = "one"
println(double) // Outputs "1.0"

I am guessing it should be made compliant to StringLiteralConvertible, but not sure about the details.

Paul
  • 481
  • 5
  • 15

2 Answers2

1

So, you want to natural-language-parse a string, and generate a floating-point number from it?

Well, the extension is the easy part. Just create a failable initializer for it:

let digits = [
    "zero", "one", "two", "three",
    "four", "five", "six", "seven",
    "eight", "nine",
]

extension Double {
    init?(fromEnglishString s: String) {
        if let digit = find(digits, s) {
            self.init(Double(digit))
        }
        else {
            return nil
        }
    }
}

let d = Double(fromEnglishString: "one")
// d is {Some 1.0}

The hard part is going to be finding a good parser for all the ways you can express numbers in English (especially floating-point numbers). That's much more tricky. You might find this more language-agnostic answer interesting.

You could also write a StringLiteralConvertible extension for it. However, this is only for when you are initializing your value directly from a string literal at compile time – which would be a bit pointless, I mean, do you really need word-based number literals in your source code? The other problem is literal convertible initializers can't be failable, so you'll be stuck with returning a default value (maybe NaN?) if the string can't be parsed.

Nevertheless, if you really want one:

extension Double: StringLiteralConvertible {
    public typealias StringLiteralType = String
    public typealias UnicodeScalarLiteralType = String
    public typealias ExtendedGraphemeClusterLiteralType = String

    public init(unicodeScalarLiteral value: UnicodeScalarLiteralType) {
        self.init(stringLiteral: value)
    }

    public init(extendedGraphemeClusterLiteral value: ExtendedGraphemeClusterLiteralType) {
        self.init(stringLiteral: value)
    }

    public init(stringLiteral value: String) {
        if let d = Double(fromEnglishString: value) {
            self = d
        } else {
            self = 0.0
        }
    }
}

let doubleFromLiteral: Double = "three"
// doubleFromLiteral is {Some 3.0}
Community
  • 1
  • 1
Airspeed Velocity
  • 40,491
  • 8
  • 113
  • 118
  • Nice solution. up voted. I think I will delete my pour answer. kkkk – Leo Dabus Dec 30 '14 at 22:52
  • yours is fine :) just depends whether you consider it something you should initiate the source type (like `String.toInt`) or the destination type (like `Double.init(_ other: Float80)` or `init?(rawValue: RawValue)` for Enums)... – Airspeed Velocity Dec 30 '14 at 23:27
  • StringLiteralConvertible extension example is great! Thanx! – Paul Dec 30 '14 at 23:50
0

If you want to do exactly what your code example does... Write an extension that implements the StringLiteralConvertible protocol. There's a decent write up on the literal convertibles at NSHipster.

It'd probably look something like this:

extension Double: StringLiteralConvertible {
    convenience init(value: String) {
        if value == "one" {
            self = 1.0
        /*
        Add more English-to-number conversion magic here
        */
        } else {
            return NaN
        }
    }
}

There's a bit more to it than that — StringLiteralConvertible extends a couple of other protocols whose requirements you have to meet, and then there's the whole business of translating English to numbers. You may have a feasibility problem there, but making Doubles from strings is technically possible.

On top of all that, there are more questions as to whether this is a good idea.

  • Literal initializers can't fail, so you have to return a sentinel value for strings you can't parse a number from. Not very swifty.
  • Do you actually want to convert string literals, or strings passed in at runtime? The former doesn't seem super useful. The latter requires different syntax at the call site, but lets you make clear that you're defining/using a conversion function.
rickster
  • 124,678
  • 26
  • 272
  • 326
  • This won't work. `StringLiteralConvertible` requires an `init` not an `init?`. Also, if you wanted to create doubles from strings at runtime (i.e. not from literals but from say user input) you shouldn't use `StringLiteralConvertible`. – Airspeed Velocity Dec 30 '14 at 22:01
  • 1
    That's what I get for answering from memory on the phone. :-/ Must've been confusing literal initializers with enum initializers... Edit forthcoming. – rickster Dec 30 '14 at 22:11