Extension for Double to accept String as initializer in Swift - swift

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.

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}

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.

Related

Write a String init in swift

In my code, an array of [Int] of size 3 has a special meaning. I want to get its string representation.
The most idiomatic in swift seems to me to be writing a new String initializer.
Something like this :
extension String {
public init(point: [Int]) {
assert(condition: point.count == 3)
let r = "x=\(point[0]) y=\(point[1]) z=(point[2])"
self.init(stringLiteral: r) // what should I write here ?? This feels clumsy ?
}
What should go at the end of this init ? I can't assign to self, and there's no other obvious init that I should call.
First of all there is a backslash missing in the String Interpolation line.
Just call self.init with r as parameter. Technically it's a convenience initializer.
extension String {
public init(point: [Int]) {
assert(point.count == 3)
let r = "x=\(point[0]) y=\(point[1]) z=\(point[2])"
self.init(r)
}
}

Calling a method on an Optional Double to convert to String

I've written this in order to easily format floating point numbers as strings with various levels of precision.
extension FloatingPoint {
func str(_ precision: Int) -> String {
return String(format: "%."+String(precision)+"f", self as! CVarArg)
}
}
It works great for non-optional variables with floating point types:
var myDouble: Double = 3.1415
var text = myDouble.str(2) // sets text = "3.14"
Is there a way of getting something like this to work for an optional Double?
var myNilDouble: Double? = nil
var text = myNilDouble.str(2) // I'd like it to set text = ""
I'd like the implementation to support nil and non-nil conversion to string.
You're not calling the method on a Double, you're calling it on an Optional<Double>; completely different type. So you need the method to exist on Optional:
extension Optional where Wrapped : FloatingPoint {
func string(usingPrecision precision: Int) -> String {
guard let double = self else { return "" }
return double.str(precision)
}
}
I don't like this interface, though; I don't think it's Optional's job to decide to emit an empty string. Instead I would suggest an initializer on String itself that takes an Optional floating point:
import Foundation
extension String {
init<N : FloatingPoint>(_ value: N?, precision: Int) {
guard let value = value else { self.init(); return }
self.init(format: "%.\(precision)f", value as! CVarArg)
}
}
Now you write
let value: Double? = nil
let text = String(value, precision: 2) // ""
Another option is a free function, again taking an Optional. (You can also of course make it the caller's choice as to what nil resolves to, as Sulthan said.)
The simplest solution would be to use optional chaining and nil coalescing:
var text = myNilDouble?.str(2) ?? ""
Although you might end up with many repetitions of this pattern, the advantage is that you have better control over the nil scenario, maybe in some situations you'll want to use "(null)" as default value.

define custom integer type in swift 2.2

I'm making a little "adress book" in Swift 2.2 and I wanted to make a custom type for phone numbers (which would always be positive numbers of 10 digits).
Looking around the internet, I came up with this:
typealias phoneNumber = 100000000...9999999999
but I'm not sure it would work.
How could I do that? Thanks
You could use PhoneNumberKit.
Then it would be as easy as:
do {
let phoneNumber = try PhoneNumber(rawNumber:"+33 6 89 017383")
}
catch {
print("Generic parser error")
}
Use a class or struct to hold the digits instead, and then have the validation routines for what makes a correct number as part of the initailizer.
Firs of all these are very big numbers, even a 32 bit integer is not enough to store 9999999999, and the compiler will display an error that it overflows.
Maybe you should make a PhoneNumber type and make it IntegerLiteralConvertible to create it like it is a number. As mentioned above, you have to use 64 bit types.
public struct PhoneNumber: IntegerLiteralConvertible, CustomStringConvertible {
var phoneNumber: UInt64
public init(integerLiteral value: UInt64) {
if UInt64(100000000)...UInt64((UInt64(1000000000) * UInt64(10)) - UInt64(1)) ~= value {
phoneNumber = value
}
else {
fatalError()
}
}
public var description: String {
return "+\(phoneNumber)"
}
public func call() {
//custom implementation
}
}
You can add any other methods here like "call()". The usage is simple, but you have to explicitly tell the compiler the type.
let momsNumber: PhoneNumber = 3630123456
One of the downsides is that it may cause a runtime error.

Generic Type in Swift as Return Value

I would like to implement some code so that I can call something like:
NSUserDefaults("key1", "value1")
let s = NSUserDefaults("key1") // "value1" expected
NSUserDefaults("key2", 2.01)
let s = NSUserDefaults("key2") // 2.01 expected
I have some code in concept as below, but obviously it's not going to work. So my question is, instead of writing a series of functions like class func bool(key: String, _ v: Bool? = nil) -> Bool? is there any way to take the advantage of generic please?
extension NSUserDefaults {
class func object<T: AnyObject>(key: String, _ v: T? = nil) -> T? {
if let obj: T = v {
NSUserDefaults.standardUserDefaults().setObject(obj, forKey: key)
NSUserDefaults.standardUserDefaults().synchronize()
} else {
return NSUserDefaults.standardUserDefaults().objectForKey(key) as T?
}
return v
}
}
Your syntax is going to wind up being very poor. This line can't work as written:
let s = NSUserDefaults("key1") // "value1" expected
Swift has to pick a type for s at compile time, not run time. So the only type it can assign here is Any (not even AnyObject is expansive enough if you want to return Double since Double is not AnyObject).
That means you have to explicitly call out let s : Any = ... (because Swift wisely won't let you create Any implicitly), and then you're going to wind up with an Any that you have to type-check somehow. When you're done, you're going to come full circle to objectForKey().
Even if you could get this syntax working, you shouldn't try to overload a single function syntax to do opposite things. That's very confusing. If you were going to build an extension like this, you should probably make it a subscript. That way you'd say defaults["key1"] and defaults["key2"] = 2.01. That's something may be able to build (though there will still be type annotation headaches required to deal with AnyObject?).

NSUserDefaults in Swift - implementing type safety

One of the things that bugs me about Swift and Cocoa together is working with NSUserDefaults, because there is no type information and it is always necessary to cast the result of objectForKey to what you are expecting to get. It is unsafe and impractical. I decided to tackle this problem, making NSUserDefaults more practical in Swift-land, and hopefully learning something along the way. Here were my goals in the beginning:
Complete type safety: each key has one type associated with it. When setting a value, only a value of that type should be accepted and when getting a value the result should come out with the correct type
Global list of keys which are clear in meaning and content. The list should be easy to create, modify and extend
Clean syntax, using subscripts if possible. For example, this would
be perfect:
3.1. set: UserDefaults[.MyKey] = value
3.2. get: let value = UserDefaults[.MyKey]
Support for classes that conform to the NSCoding protocol by
automatically [un]archiving them
Support for all property list types accepted by NSUserDefaults
I started by creating this generic struct:
struct UDKey <T> {
init(_ n: String) { name = n }
let name: String
}
Then I created this other struct that serves as a container for all the keys in an application:
struct UDKeys {}
This can then be extended to add keys wherever needed:
extension UDKeys {
static let MyKey1 = UDKey<Int>("MyKey1")
static let MyKey2 = UDKey<[String]>("MyKey2")
}
Note how each key has a type associated with it. It represents the type of the information to be saved. Also, the name property is the string that is to be used as a key for NSUserDefaults.
The keys can be listed all in one constants file, or added using extensions on a per-file basis close to where they are being used for storing data.
Then I created an "UserDefaults" class responsible for handling the getting/setting of information:
class UserDefaultsClass {
let storage = NSUserDefaults.standardUserDefaults()
init(storage: NSUserDefaults) { self.storage = storage }
init() {}
// ...
}
let UserDefaults = UserDefaultsClass() // or UserDefaultsClass(storage: ...) for further customisation
The idea is that one instance for a particular domain is created and then every method is accessed in this way:
let value = UserDefaults.myMethod(...)
I prefer this approach to things like UserDefaults.sharedInstance.myMethod(...) (too long!) or using class methods for everything. Also, this allows interacting with various domains at the same time by using more than one UserDefaultsClass with different storage values.
So far, items 1 and 2 have been taken care of, but now the difficult part is starting: how to actually design the methods on UserDefaultsClass in order to comply with the rest.
For example, let's start with item 4. First I tried this (this code is inside UserDefaultsClass):
subscript<T: NSCoding>(key: UDKey<T>) -> T? {
set { storage.setObject(NSKeyedArchiver.archivedDataWithRootObject(newValue), forKey: key.name) }
get {
if let data = storage.objectForKey(key.name) as? NSData {
return NSKeyedUnarchiver.unarchiveObjectWithData(data) as? T
} else { return nil }
}
}
But then I find out that Swift doesn't allow generic subscripts!! Alright, then I guess I'll have to use functions then. There goes half of item 3...
func set <T: NSCoding>(key: UDKey<T>, _ value: T) {
storage.setObject(NSKeyedArchiver.archivedDataWithRootObject(value), forKey: key.name)
}
func get <T: NSCoding>(key: UDKey<T>) -> T? {
if let data = storage.objectForKey(key.name) as? NSData {
return NSKeyedUnarchiver.unarchiveObjectWithData(data) as? T
} else { return nil }
}
And that works just fine:
extension UDKeys { static let MyKey = UDKey<NSNotification>("MyKey") }
UserDefaults.set(UDKeys.MyKey, NSNotification(name: "Hello!", object: nil))
let n = UserDefaults.get(UDKeys.MyKey)
Note how I can't call UserDefaults.get(.MyKey). I have to use UDKeys.MyKey. And I can't do that because it's not yet possible to have static variables on a generic struct!!
Next, let's try number 5. Now that has been an headache and that's where I need lots of help.
Property list types are, as per the docs:
A default object must be a property list, that is, an instance of (or
for collections a combination of instances of): NSData, NSString,
NSNumber, NSDate, NSArray, or NSDictionary.
That in Swift means Int, [Int], [[String:Bool]], [[String:[Double]]], etc are all property list types. At first I thought that I could just write this and trust whoever is using this code to remember that only plist types are allowed:
func set <T: AnyObject>(key: UDKey<T>, _ value: T) {
storage.setObject(value, forKey: key.name)
}
func get <T: AnyObject>(key: UDKey<T>) -> T? {
return storage.objectForKey(key.name) as? T
}
But as you'll notice, while this works fine:
extension UDKeys { static let MyKey = UDKey<NSData>("MyKey") }
UserDefaults.set(UDKeys.MyKey, NSData())
let d = UserDefaults.get(UDKeys.MyKey)
This doesn't:
extension UDKeys { static let MyKey = UDKey<[NSData]>("MyKey") }
UserDefaults.set(UDKeys.MyKey, [NSData()])
And this doesn't either:
extension UDKeys { static let MyKey = UDKey<[Int]>("MyKey") }
UserDefaults.set(UDKeys.MyKey, [0])
Not even this:
extension UDKeys { static let MyKey = UDKey<Int>("MyKey") }
UserDefaults.set(UDKeys.MyKey, 1)
The problem is that they are all valid property list types yet Swift obviously interprets arrays and ints as structs, not as their Objective-C class counterparts. However:
func set <T: Any>(key: UDKey<T>, _ value: T)
won't work either, because then any value type, not just the ones that have a class cousin courtesy of Obj-C, is accepted, and storage.setObject(value, forKey: key.name) is no longer valid because value has to be a reference type.
If a protocol existed in Swift that accepted any reference type and any value type that can be converted to a reference type in objective-c (like [Int] and the other examples I mention) this problem would be solved:
func set <T: AnyObjectiveCObject>(key: UDKey<T>, _ value: T) {
storage.setObject(value, forKey: key.name)
}
func get <T: AnyObjectiveCObject>(key: UDKey<T>) -> T? {
return storage.objectForKey(key.name) as? T
}
AnyObjectiveCObject would accept any swift classes and swift arrays, dictionaries, numbers (ints, floats, bools, etc that convert to NSNumber), strings...
Unfortunately, AFAIK this doesn't exist.
Question:
How can I have write a generic function (or collection of overloaded generic functions) whose generic type T can be any reference type or any value type that Swift can convert to a reference type in Objective-C?
Solved: With the help of the answers I got, I arrived at what I wanted. In case anyone wants to take a look at my solution, here it is.
I don't mean to brag but ... oh who am I kidding, I totally do!
Preferences.set([NSData()], forKey: "MyKey1")
Preferences.get("MyKey1", type: type([NSData]))
Preferences.get("MyKey1") as [NSData]?
func crunch1(value: [NSData])
{
println("Om nom 1!")
}
crunch1(Preferences.get("MyKey1")!)
Preferences.set(NSArray(object: NSData()), forKey: "MyKey2")
Preferences.get("MyKey2", type: type(NSArray))
Preferences.get("MyKey2") as NSArray?
func crunch2(value: NSArray)
{
println("Om nom 2!")
}
crunch2(Preferences.get("MyKey2")!)
Preferences.set([[String:[Int]]](), forKey: "MyKey3")
Preferences.get("MyKey3", type: type([[String:[Int]]]))
Preferences.get("MyKey3") as [[String:[Int]]]?
func crunch3(value: [[String:[Int]]])
{
println("Om nom 3!")
}
crunch3(Preferences.get("MyKey3")!)
I'd like to introduce my idea. (Sorry for my poor English in advance.)
let plainKey = UDKey("Message", string)
let mixedKey
= UDKey("Mixed"
, array(dictionary(
string, tuple(
array(integer),
optional(date)))))
let ud = UserDefaults(NSUserDefaults.standardUserDefaults())
ud.set(plainKey, "Hello")
ud.set(plainKey, 2525) // <-- compile error
ud.set(mixedKey, [ [ "(^_^;)": ([1, 2, 3], .Some(NSDate()))] ])
ud.set(mixedKey, [ [ "(^_^;)": ([1, 2, 3], .Some(NSData()))] ]) // <-- compile error
The only difference is that UDKey() now requires #2 argument, a value of BiMap class. I've uncoupled the work originally of UDKey into BiMap which converts a value of a type to/from a value of another type.
public class BiMap<A, B> {
public func AtoB(a: A) -> B?
public func BtoA(b: B) -> A?
}
Consequently, types that set/get can accepts are conducted by BiMap, and no longer limited to types as can automatically cast
from/to AnyObject (more specifically, types NSUserDefaults can accepts.).
Because BiMap is a generic class, you can easily create subtypes of that, interchanging arbitrary two types you want.
Here is full source code. (But there are bugs yet to be fixed..)
https://gist.github.com/hisui/47f170a9e193168dc946