Swift: Precedence for custom operator in relation to dot (".") literal - ios

In Swift 3, I have written a custom operator prefix operator § which I use in a method taking a String as value returning a LocalizedString struct (holding key and value).
public prefix func §(key: String) -> LocalizedString {
return LocalizedString(key: key)
}
public struct LocalizedString {
public var key: String
public var value: String
public init(key: String) {
let translated = translate(using: key) // assume we have this
self.key = key
self.value = translated ?? "!!\(key)!!"
}
}
(Yes I know about the awesome L10n enum in SwiftGen, but we are downloading our strings from our backend, and this question is more about how to work with custom operators)
But what if we wanna get the translated value from the result of the § operator (i.e. the property value from the resulting LocalizedString)
let translation = §"MyKey".value // Compile error "Value of type 'String' has no member 'value'"
We can of course easily fix this compile error by wraping it in parenthesis (§"MyKey").value. But if do not want to do that. Is it possible to set precedence for custom operators in relationship to the 'dot' literal?
Yes I know that only infix operators may declare precedence, but it would make sense to somehow work with precedence in order to achieve what I want:
precedencegroup Localization { higherThan: DotPrecedence } // There is no such group as "Dot"
prefix operator §: Localization
To mark that the Swift compiler first should evaluate §"MyKey" and understand that is not a string, but in fact an LocalizedString (struct).
Feels unlikely that this would be impossible? What am I missing?

The . is not an operator like all the other ones defined in the standard library, it is provided by the compiler instead. The grammar for it are Explicit Member Expressions.
Having a higher precedence than the . is nothing the compiler should enable you to do, as it's such a fundamental use case. Imagine what you could do if the compiler enabled such a thing:
-"Test".characters.count
If you could have a higher precedence than ., the compiler has to check all possibilities:
(-"Test").characters.count // func -(s: String) -> String
(-("Test".characters)).count // func -(s: String.CharacterView) -> String.CharacterView
-("Test".characters.count) // func -(s: Int) -> Int
Which would
Potentially increase the compile time a lot
Be ambiguous
Possibly change behaviour of existing code upon adding overloads
What I suggest you to do is abandon the idea with a new operator, it's only going to be adding more cognitive load by squashing some specific behaviour into a single obscure character. This is how I'd do it:
extension String {
var translatedString : String {
return translate(using: self)
}
}
"MyKey".localizedString
Or if you want to use your LocalizedString:
extension String {
var localized : LocalizedString {
return LocalizedString(key: self)
}
}
"MyKey".localized.value
These versions are much more comprehensive.

Related

Can #dynamicMemberLookup be used to call methods?

In the documentation for #dynamicMemberLookup it says,
Apply this attribute to a class, structure, enumeration, or protocol to enable members to be looked up by name at runtime.
If I'm not mistaken, instance methods are considered members of a struct / class. However, when I try to call a function dynamically I get an error saying:
Dynamic key path member lookup cannot refer to instance method foo()
To reproduce the problem:
struct Person {
var name: String
var age: Int
func greet() {
print("hello, my name is \(name)")
}
}
#dynamicMemberLookup
struct Wrapper {
var value: Person
subscript<T>(dynamicMember keypath: KeyPath<Person, T>) -> T {
value[keyPath: keypath]
}
}
let person = Person(name: "John Doe", age: 21)
let wrapper = Wrapper(value: person)
wrapper.greet() // << Error: Dynamic key path member lookup cannot refer to instance method `greet()`
// Or
let function = wrapper.greet // << Error: Dynamic key path member lookup cannot refer to instance method `greet()`
function()
How can I dynamically call greet() using #dynamicMemberLookup? Is there any way to achieve what I'm trying to do?
Thanks in advance!
No, dynamicMemberLookup does not work for methods. As the signature of the subscript suggests, it only works for things that can be represented as a KeyPath. Method calls cannot be part of a key path. :(
Key-Path Expression
A key-path expression refers to a property or subscript of a type.
The path consists of property names, subscripts, optional-chaining
expressions, and forced unwrapping expressions. Each of these key-path
components can be repeated as many times as needed, in any order.
At compile time, a key-path expression is replaced by an instance of
the KeyPath class.
I suspect the reason why it is called "dynamic member lookup" is because it also works with subscripts. The alternative of dynamicPropertyOrSubscriptLookup is rather a mouthful isn't it?
One rather hacky fix would be to change greet into a computed property:
var greet: () -> Void { {
print("hello, my name is \(name)")
} }
If greet has had parameters, you could also change it into a subscript, but I think that is an even uglier solution.

Swift Generic parameters and return types

When working with Swift Generics, do the type placeholders all have to be the same types? The example below turns all type placeholders into a string type. Is it possible for the parameter to be a string and the return type be an Int or do Generics work where each placeholder is the of the same type?
Here's the example:
func takeAndReturnSameThing<T>(t: T) -> T {
return t
}
Let thing = takeAndReturnSameThing("howdy")
Yes
The whole point of type parameters ("place holders") is that they consistently represent the same type.
If you want to represent multiple types generically, you introduce new type parameters.
func takeAndReturnADifferentThing<T, U>(t: T) -> U {
return t.getU()
}
You can have multiple generic types like this:
func takeAndReturnDifferentThing<T,U>(t:T) -> U {
return t.convertToU() // Assuming type T has this method.
}
Of course you should constrain both T and S to make sure that T can be converted to S.

Why CustomStringConvertible protocol not working for Int?

public func +<T: CustomStringConvertible>(lhs: T, rhs: T)->String{
return lhs.description+rhs.description
}
let a:String = "A"
let i:Int = 0
print(a+i)
I am overloading '+' operator for CustomStringConvertible types. String and Int both confirms CustomStringConvertible protocol but it gives an error: "binary operator '+' cannot be applied to operands of type 'String' and 'Int' print(a+i)". It working fine when I apply it to 'String'+'NSNumber'.
don't know what is going behind the scene. why it is not working?
The problem is firstly (believe it or not) String doesn't conform to CustomStringConvertible. You'll therefore want to conform it yourself in order for it to return self for description (probably easier than writing another overload to deal with strings independently).
extension String:CustomStringConvertible {
public var description: String {
return self
}
}
Secondly, you need two generic parameters for your + overload, in order to allow it to accept different types for both parameters, while ensuring that both parameters conform to CustomStringConvertible:
public func +<T: CustomStringConvertible, U:CustomStringConvertible>(lhs: T, rhs: U)->String{
return lhs.description+rhs.description
}

Complicated Swift Extension

I want to create an extension for the String class in Swift that allows you to get a substring via the subscript operator like in Python. This can be accomplished with the Range class in the following way
extension String {
subscript (range: Range<Int>) -> String? {
if range.startIndex < 0 || range.endIndex > count(self) {
return nil
}
let range = Range(start: advance(startIndex, range.startIndex), end: advance(startIndex, range.endIndex))
return substringWithRange(range)
}
}
This enables the following usage
let string = "OptimusPrime"
string[0...6] // "Optimus"
However, I want to be able to index the string from the end as well as the beginning using negative integers.
string[7...(-1)] // "Prim"
Since the Range class doesn't allow startIndex to be greater than endIndex the extension above is not sufficient to achieve this behavior.
Since Range objects apparently can't be used for this extension, it makes sense to me to simply use the same syntax as in Python
string[0:2] // "Op"
string[0:-1] // "OptimusPrim"
Is this possible? How would I go about doing this?
i think you could get python-like syntax like this:
subscript (start:Int, end:Int) -> String?
which would let you go "bla bla"[0,3]
then if you take a negative number in, just make convert it to the string length minus the negative number instead so the range is valid

Turn a string into a variable

Hello I have a for in loop where elements is the variable being changed and in this case "elements" is a string but there is a corresponding variable out side of the for in loop that has the same name as the string called elements. So what I mean is out side there is a Var time = [some,text,words] and theres a for in loop that calls a STRING named "time" and I would like to know how to convert the string in the for in loop into the variable by some how taking off the "'s (not that simple I know) without specifically saying "time"(the variable) but instead converting the "elements"(which is the string 'time') string into the variable. I hope I was clear enough if I'm not making sense I'll try again.
You cannot refer to local variables dynamically by their names in Swift. This would break a lot of compiler optimizations as well as type safety if you could.
You can refer to object properties by their names if the class conforms to key-value coding. For example:
class X : NSObject {
let time = ["some", "text", "words"]
func readWordsFromProp(name: String) -> String {
guard let list = self.valueForKey(name) as? [String] else {
return ""
}
var result = ""
for word in list {
result += word
}
return result
}
}
let x = X()
print(x.readWordsFromProp("time"))
In general, there are better ways to do things in Swift using closures that don't rely on fragile name-matching. But KVC can be a very powerful tool

Resources