Can a condition be used to determine the type of general?

First, I will explain what I'm trying to do, and how I got to the place where I was stuck before moving on to the question.


As a training exercise for myself, I took some problems that I had already solved in Objective-C to see how I can solve them differently using Swift. The specific case I'm stuck with is a small part that captures the value before and after it changes and interpolates between them to create keyframes for the animation.

To do this, I had a Capture object with properties for the object, a key, and two id properties for the values ​​before and after. Later, when interpolating the captured values, I made sure that they could be interpolated by wrapping each of them with the Value class, which used a class cluster to return the corresponding class depending on the type of value that it wrapped, or nil for types that were not supported.

This works, and I can make it work in Swift, as well as in the same way, but it doesn't feel like Swift.


What worked

Instead of wrapping captured values ​​as a way to enable interpolation, I created a Mixable protocol that types could match and use a protocol extension when the type supported the necessary basic arithmetic:

 protocol SimpleArithmeticType { func +(lhs: Self, right: Self) -> Self func *(lhs: Self, amount: Double) -> Self } protocol Mixable { func mix(with other: Self, by amount: Double) -> Self } extension Mixable where Self: SimpleArithmeticType { func mix(with other: Self, by amount: Double) -> Self { return self * (1.0 - amount) + other * amount } } 

This part worked very well and provided uniform mixing (that the type could only be mixed with its own type), which was not performed in the Objective-C implementation.

Where am i stuck

The next logical step, and it was here that I was stuck, it seemed that each instance of Capture (now a structure) contained two variables of the same mixed type instead of two AnyObject . I also changed the initializer argument from the object and the key closure path that returns the object ()->T

 struct Capture<T: Mixable> { typealias Evaluation = () -> T let eval: Evaluation let before: T var after: T { return eval() } init(eval: Evaluation) { self.eval = eval self.before = eval() } } 

This works when a type can be inferred, for example:

 let captureInt = Capture { return 3.0 } // > Capture<Double> 

but not with the key value that AnyObject returns: \

 let captureAnyObject = Capture { return myObject.valueForKeyPath("opacity")! } 

error: cannot initialize for type 'Capture' using argument list of type '(() → _)'

AnyObject does not comply with the Mixable protocol, so I can understand why this is not working. But I can check what type of object really is, and since I cover only a few heterogeneous types, I could cover all cases and return the correct Capture type. Too, if that might even work, I made an even simpler example.

Simplest example

 struct Foo<T> { let x: T init(eval: ()->T) { x = eval() } } 

which works when type inference is guaranteed:

 let fooInt = Foo { return 3 } // > Foo<Int> let fooDouble = Foo { return 3.0 } // > Foo<Double> 

But not when a closure can return different types

 let condition = true let foo = Foo { if condition { return 3 } else { return 3.0 } } 

error: cannot call an initializer for type 'Foo' with a list of arguments of type '(() → _)'

I cannot even determine such a closure myself.

 let condition = true // as simple as it could be let evaluation = { if condition { return 3 } else { return 3.0 } } 

error: unable to specify closure type in current context

My question

Is this something that can be done at all? Can a condition be used to determine the type of general? Or is there another way to hold two variables of the same type where the type was determined based on the condition?


Edit

I really want:

  • captures the values ​​before and after the change and saves the pair (old + new) for the later (a heterogeneous set of homogeneous pairs).
  • go through all the collected values ​​and get rid of those that cannot be interpolated (if this step cannot be integrated with the collection step)
  • interpolate each homogeneous pair individually (mixing old + new).

But it seems that this direction is a dead end when it comes to solving this problem. I need to take a couple of steps back and try a different approach (and maybe ask a different question if I get stuck again).

+4
source share
2 answers

As discussed on Twitter , the type must be known at compile time. However, for a simple example, at the end of the question, you can simply explicitly enter

let evaluation: Foo<Double> = { ... }

and it will work.

So, in the case of Capture and valueForKeyPath: IMHO, you should use (safely or forcefully) a value of type Mixable , which you expect from the value, and it should work fine. In the end, I'm not sure that valueForKeyPath: should return different types depending on the condition.

What is the exact case when you would like to return 2 completely different types (which cannot be implicitly entered as in the simple case of Int and Double above) in the same evaluation closure?

+4
source

in my full example, I also have cases for CGPoint, CGSize, CGRect, CATransform3D

The limitations are exactly the same as you stated, due to the strict typing of Swift. All types must be definitely known at compile time, and each thing can have only one type - even a general type (it is allowed the way it is called at compile time). Thus, the only thing you can do is turn your type into an umbrella type that looks more like Objective-C:

 let condition = true let evaluation = { () -> NSObject in // * if condition { return 3 } else { return NSValue(CGPoint:CGPointMake(0,1)) } } 
0
source

All Articles