语法的泛型参数,  Swift的泛型有点奇怪

作者: 编程  发布:2019-11-29

  They said "you should learn a new language every year," so I  learned Swift. Now  I  learn a new language every two weeks!

  本篇纯属抬杠之作,之前我们提到了Swift的泛型Protocol使用associatedtype关键字,而不是使用<Type>语法的泛型参数。这其中有什么好处呢?

  这个笑话绝对是我看过的Swift被黑的最惨的一次!所以今天我们来学习一下Swift的泛型。

  我就这个问题搜索了一些回答,大体上提到两点:

  Swift的泛型有点奇怪,针对Class和Function,都是通过<Type>来定义,和C#一摸一样,同样也有where关键字进行约束。

  <Type>语法对Protocol没有意义,Protocol仅需要定义一个抽象的概念,具体的类型应该由实现的Class来明确,比如:

func swapTwoValues<T>(inout a: T, inout _ b: T) {
    let temporaryA = a
    a = b
    b = temporaryA
}

class CanPrintBase<T> {
    func PrintType(output: T) -> Void {}
}
ClassWithInt<Int>: NumberProtocol
ClassWithDouble<Double>: NumberProtocol

  但是面对Interface,也就是Swift里的Protocol,需要使用associatedtype关键字来定义泛型:

  associatedtype可以用来给Protocol中特定Func添加泛型约束,而不是限定整个Protocol

protocol CanPrint {
    
    associatedtype E
    
    func PrintType(output: E) -> Void
}
protocol GeneratorType {
    associatedtype Element
    public mutating func next() -> Self.Element?
}

  那要怎么去实现这个接口呢?通常是这样子的:

  听上去还是有一定道理的,然后实践是检验事实的唯一标准。下面我们通过代码实例来和C#进行对比。首先拿出网上多被引用解释上述两个观点的Swift代码:

class TypePrinter0 : CanPrint{
    
    typealias E = String
    
    func PrintType(output: E) {
        print(type(of:output))
    }
}

let print0 = TypePrinter0()
print0.PrintType(output: "String Type")
public protocol Automobile {
    associatedtype FuelType
    associatedtype ExhaustType
    func drive(fuel: FuelType) -> ExhaustType
}
public protocol Fuel {
    associatedtype ExhaustType
    func consume() -> ExhaustType
}
public protocol Exhaust {
    init()
    func emit()
}

public struct UnleadedGasoline<E: Exhaust>: Fuel {
    public func consume() -> E {
        print("...consuming unleaded gas...")
        return E()
    }
}
public struct CleanExhaust: Exhaust {
    public init() {}
    public func emit() {
        print("...this is some clean exhaust...")
    }
}
public class Car<F: Fuel,E: Exhaust>: Automobile where F.ExhaustType == E {
    public func drive(fuel: F) -> E {
        return fuel.consume()
    }
}

public class Car1<F: Fuel>: Automobile {
    public func drive(fuel: F) -> F.ExhaustType {
        return fuel.consume()
    }
}

  然后就会在output窗口打印“String”。

  具体的使用情况如下:

  阿西吧!这么奇怪的语法简直不能忍!就不能用<Type>来写吗?

var car = Car<UnleadedGasoline<CleanExhaust>, CleanExhaust>()
car.drive(fuel: UnleadedGasoline<CleanExhaust>()).emit()

var fusion = Car1<UnleadedGasoline<CleanExhaust>>()
fusion.drive(fuel: UnleadedGasoline<CleanExhaust>()).emit()

  曲线救国的话,我们可以考虑模拟一个抽象类class CanPrintBase<T>,通过继承来实现同样的效果:

  转换成C#代码的话,有两种思路,首先是把泛型参数放到Interface层面:

class TypePrinter3: CanPrintBase<String>{
    
    override func PrintType(output: String){
         print(type(of:output))
    }
}

let print3 = TypePrinter3()
print3.PrintType(output: "String Type")
    public interface Automobile<FuelType, ExhaustType>
    {
        ExhaustType Drive(FuelType fuel);
    }
    public interface Fuel<ExhaustType>
    {
        ExhaustType consume();
    }
    public interface Exhaust 
    {
        void Emit();
    }

    public class UnleadedGasoline<Exhaust> : Fuel<Exhaust> where Exhaust : new()
    {
        public Exhaust consume()
        {
            Console.WriteLine("...consuming unleaded gas...");
            return new Exhaust();
        }
    }
    public class CleanExhaust : Exhaust
    {
        public void Emit()
        {
            Console.WriteLine("...this is some clean exhaust...");
        }
    }
    public class Car : Automobile<UnleadedGasoline<CleanExhaust>, CleanExhaust>
    {
        public CleanExhaust Drive(UnleadedGasoline<CleanExhaust> fuel)
        {
            return fuel.consume();
        }
    }

  那么我们像C#一样直接在类定义的时候通过占位符的方式可以嘛?

  还可以模仿Swift对Automobile多做一层继承进行包装:

//This one cannot work!
class TypePrinter1<E: String> : CanPrint{
   
    func PrintType(output: E) {
        print(output)
    }
}
    public interface Car1<T1> : Automobile<UnleadedGasoline<T1>, T1> where T1 : new()
    {

    }

    public class SimpleCar : Car1<CleanExhaust>
    {
        public CleanExhaust Drive(UnleadedGasoline<CleanExhaust> fuel)
        {
            return fuel.consume();
        }
    }

  错误提示为:Inheritance from non-protocol, non-class type 'String'。也就是说如果是class类型的话就可以:

调用的时候没有什么太大的差别:

public class SomeType {}

class TypePrinter2<E: SomeType> : CanPrint{

    func PrintType(output: E) {
        print(output)
    }

}

let print2 = TypePrinter2()
print2.PrintType(output: SomeType())
  var gaso = new UnleadedGasoline<CleanExhaust>();
  var car = new Car();
  car.Drive(gaso).Emit();

  var simpleCar = new SimpleCar();
  simpleCar.Drive(gaso).Emit();

  反之我们也可以写成这样:

9159.com,  和Swift比较不同的是,我们在Interface就代入了泛型参数。但是由于我们不能直接实例化Interface,所以并不能直接使用Automobile来减少一层继承关系。

class TypePrinter5 : CanPrint{
    
    typealias E = SomeType
    
    func PrintType(output: E) {
        print(output)
    }
}

let print5 = TypePrinter5();
print(type(of: print5))
print(type(of: print2))

  因为上述提到的使用associatedtype 的第一点理由见仁见智,这里不分高下。

  将类型打印出来的话,分别是TypePrinter5和TypePrinter2<SomeType>,也就是说这两种写法得到的类型是完全不一样的。

  C#还有第二种思路,就是我也把泛型约束下放到Func层级:

  呵呵也是蛮妖的嘛,还可以把类型的具体定义留到使用时再声明:

    public interface Automobile
    {
        ExhaustType Drive<FuelType,ExhaustType>(FuelType fuel) where ExhaustType : new();
    }
    public interface Fuel
    {
        ExhaustType consume<ExhaustType>() where ExhaustType : new();
    }

    public class UnleadedGasoline : Fuel
    {
        public Exhaust consume<Exhaust>() where Exhaust : new()
        {
            Console.WriteLine("...consuming unleaded gas...");
            return new Exhaust();
        }
    }

    public class Car2 : Automobile
    {
        public CleanExhaust Drive<UnleadedGasoline, CleanExhaust>(UnleadedGasoline fuel) where CleanExhaust : new()
        {
            return  (fuel as Fuel).consume<CleanExhaust>();
        }
    }
class TypePrinter4<E> : CanPrint{
    
    func PrintType(output: E) {
        print(output)
    }
}
let print4 = TypePrinter4<SomeType>()
print4.PrintType(output: SomeType())

let print6 = TypePrinter4<String>()
print6.PrintType(output: "I am a String")

C#的接口并不能定义构造函数。强行模仿起来还真是有点累啊。最终的使用也很简单:

  这一点又和C#傻傻分不清楚了。

    var fuel = new UnleadedGasoline();
    var car2 = new Car2();
    car2.Drive<UnleadedGasoline,CleanExhaust>(fuel).Emit();

  本篇实在是蛮无聊的纠缠与Swift泛型协议的语法,如孔乙己般尝试了回字的N种写法。至于为什么Swift要这么设计,我们下一篇可以尝试和C#对比看看。

  通篇比较下来,应该说Swift通过associatedtype 关键字和<Type>的混用,使得泛型的定义更为复杂也更灵活了。

  GitHub:

  GitHub:

  

 

 

 

 

本文由9159.com发布于编程,转载请注明出处:语法的泛型参数,  Swift的泛型有点奇怪

关键词:

上一篇:1.判断是否数字,不含其它字符
下一篇:没有了