关联类型的形式为类型的引用进而进行约束提供了条件;

同时能够简化语法形式。

Swift: Associated Types

http://www.russbishop.net/swift-associated-types

Associated Types Series

Sometimes I think type theory is deliberately obtuse and all those functional programming hipster kids are just bull-shitting as if they understood one word of it. Really? You have a 5,000 word blog post about insert random type theory concept? And it doesn't explain a) why anyone should care and b) what problems are solved by this wonderful concept's introduction? I want to tie you up in a sack, throw the sack in a river, and hurl the river into space.

Where was I? Oh yeah, Associated Types.

When I first saw Swift's implementation of generics, the use of Associated Types jumped out at me as strange.

In this post I'm going to work through the type theory and some practical considerations, mostly as an attempt to cement the concepts in my own mind (if I make any mistakes, please let me know!)

Generics

If I want to abstract over a type (aka create a generic thingy) in Swift, I use this syntax for classes:

class Wat<T> { ... }

Similarly, a generic struct:

struct WatWat<T> { ... }

Or a generic enum:

enum GoodDaySir<T> { ... }

But if I want an abstract protocol:

protocol WellINever {

typealias T

}

Huh?

Basics

Unlike classes, structs, and enums, protocols don't support generic type parameters. Instead they support abstract type members; in Swift terminology Associated Types. Though you can accomplish a lot with either system, there are some benefits to associated types (and currently some drawbacks).

An associated type in a protocol says "I don't know what exact type this is; some concrete class/struct/enum that adopts me will fill in the details".

"Great!" you cry, "so how is that different from a type parameter?" Good question. Type parameters force everyone to know the types involved and specify them repeatedly (when you compose with them it can also lead to an explosion in the number of type parameters). They're part of the public interface. The code that uses the concrete thing (class/struct/enum) makes the decision about what types to select.

By contrast an associated type is part of the implementation detail. It's hidden, just like a class can hide its internal ivars. The abstract type member is for the concrete thing (class/struct/enum) to provide later. You select the actual type when you adopt the protocol, not when you instantiate the class/struct. It leaves control over which types to select in a different set of hands.

Usefulness

Mark Odersky, creator of Scala, discusses an example in this interview. In Swift terms, without associated types if you have a base class or protocol Animal with a method eat(f:Food), then the class Cow has no way to specify that Food can only be Grass. You certainly can't do it by overloading the method - covariant parameter types (making a parameter more specific in a subclass) isn't supported in most languages and is unsafe anyway since casting to the base class would let you feed in unexpected values.

If Swift protocols did support type parameters it might look like this:

protocol Food { }

class Grass : Food { }

protocol Animal<F:Food> {

func eat(f:F)

}

class Cow : Animal<Grass> {

func eat(f:Grass) { ... }

}

Great. What happens when you need to track more than just food?

protocol Animal<F:Food, S:Supplement> {

func eat(f:F)

func supplement(s:S)

}

class Cow : Animal<Grass, Salt> {

func eat(f:Grass) { ... }

func supplement(s:Salt) { ... }

}

The increasing number of type parameters is unfortunate but that's not our only problem. We're leaking the implementation details all over the place, requiring us to re-specify the type parameters repeatedly. The type of var c = Cow() is actually Cow<Grass,Salt>. A doCowThings function would be func doCowThings(c:Cow<Grass,Salt>). What if we want to work with all animals that eat grass? We have no way to express that we don't care about the Supplement type parameter either.

When we derive from Cow to create specific breeds, our class definitions are just idiotic: class Holstein<Food:Grass, Supplement:Salt> : Cow<Grass,Salt>.

Worse, how about a function to buy food and feed the animal: func buyFoodAndFeed<T,F where T:Animal<Food,Supplement>>(a:T, s:Store<F>). Besides being really ugly and verbose, we have no way to link the type of F to Food. If we rewrite the function definition we can work around that func buyFoodAndFeed<F:Food,S:Supplement>(a:Animal<Food,Supplement>, s:Store<Food>), but it won't work anyway - Swift will complain that "'Grass' is not identical to 'Food'" when we try to pass a Cow<Grass,Salt>. Again, notice that this method doesn't care about the Supplement but it has to deal with it.

Now let's see how associated types help us:

protocol Animal {

typealias EdibleFood

typealias SupplementKind

func eat(f:EdibleFood)

func supplement(s:SupplementKind)

}

class Cow : Animal {

func eat(f: Grass) { ... }

func supplement(s: Salt) { ... }

}

class Holstein : Cow { ... }

func buyFoodAndFeed<T:Animal, S:Store

where T.EdibleFood == S.FoodType>(a:T, s:S)

{ ... }

The type signatures are much cleaner now. Swift infers the associated types just by looking at Cow's method signatures. Our buyFoodAndFeed method can clearly express the requirement that the store sells the kind of food the animal eats. The fact that Cow requires a specific kind of food is an implementation detail of the Cow class, but that information is still known at compile time.

Getting Real

Enough with the animals for a minute; let's look at Swift's CollectionType.

Note: As an implementation detail, a number of Swift protocols have nested protocols with leading underscores; CollectionType -> _CollectionType or SequenceType -> _Sequence_Type -> _SequenceType. For brevity, I'm going to flatten that hierarchy when I talk about those protocols. So when I say that CollectionType has ItemType, IndexType, and GeneratorType associated types you won't find those on the CollectionType protocol itself.

Obviously we need the type of the elements T, but we also need the type of the index and the generator/enumerator so we can handle subscript(index:S) -> T { get } and func generate() -> G<T>. If we were just using type parameters, the only way a generic Collection protocol could work is by specifying T, S, and G in a hypothetical CollectionOf<T,S,G>.

What about other languages? C# doesn't have abstract type members. It handles this firstly by not supporting anything other than open-ended indexing where the type system says nothing about whether the index can only move one direction, supports random access, etc. Numeric indexes are just integers and the type system says nothing else about them.

Secondly, for generators IEnumerable<T> spits out an IEnumerator<T>. The difference seems very subtle at first but the C# solution is using an interface (protocol) to indirectly abstract over the generator, allowing it to avoid having to specify the generator type as a parameter to IEnumerable<T>.

Swift aims to be a traditionally-compiled (non-VM, non-JIT) systems programming language so requiring that kind of dynamic behavior is not a great idea for performance. The compiler would really prefer to know the types of your index and generator so it can do fancy things like inlining and knowing how much memory to allocate. The only way that can happen is to run all these generics through the sausage grinder at compile time. If you force it to defer to runtime, that means indirection, boxing, and other such tricks which are nice when you need them but aren't free.

The Ugly Truth

There's a major "gotcha" with abstract type members: Swift won't actually let you declare them as variable or parameter types because that would be useless. The only place you can use a protocol with associated types is as a generic constraint.

In our Animal example from earlier, it isn't safe to call Animal().eat because it just takes an abstract EdibleFood and we don't know what that might be.

In theory, the code below should work since the generic constraint on the function enforces that the animal eats what the store sells, but in practice I was seeing some EXC_BAD_ACCESS crashes when testing it so I'm not sure this scenario is fully-baked.

func buyFoodAndFeed<T:Animal,S:StoreType

where T.EdibleFood == S.FoodType>(a:T, s:S) {

a.eat(s.buyFood()) //crash!

}

The inability to use these kinds of protocols as parameters or variable types is the true kicker. It just requires jumping through far too many unnecessary hoops. This is an area where Swift can (and hopefully will) improve in the future. I want the ability to declare variables or types like this:

typealias GrassEatingAnimal =

protocol<A:Animal where A.EdibleFood == Grass>

var x:GrassEatingAnimal = ...

Note: this use of typealias is actually creating a type alias, not an associated type in a protocol. Confusing, I know.

This syntax would let me declare a variable that holds some kind of Animal where the animal's associated EdibleFood is Grass. It might also be useful to allow this automatically if the associated types in the protocol are themselves constrained, but it seems like you could get into unsafe situations so that would require some more careful thought. One thing you'll run into if you do constrain the associated types in the protocol definition is the compiler will not be able to satisfy those on any method generic constraints (see below).

Currently you have to work around this by creating a wrapper struct that "erases" the associated type, exchanging it for a type parameter. Fair warning: It's ugly.

struct SpecificAnimal<F,S> : Animal {

let _eat:(f:F)->()

let _supplement:(s:S)->()

init<A:Animal where A.EdibleFood == F, A.SupplementKind == S>(var _ selfie:A) {

_eat = { selfie.eat($0) }

_supplement = { selfie.supplement($0) }

}

func eat(f:F) {

_eat(f:f)

}

func supplement(s:S) {

_supplement(s:s)

}

}

If you ever wondered why Swift's standard library includes GeneratorOf<T>:Generator, SequenceOf<T>:Sequence, and SinkOf<T>:Sink... now you know!

The bug I mentioned above is that if Animal specified typealias EdibleFood:Food then this struct can't be compiled even if you define it as struct SpecificAnimal<F:Food,S>:Animal. Swift will complain that F is not a Food even though the constraint on the struct clearly means that it is. Filed as rdar://19371678.

Wrap It Up

As we've seen, associated types allow the adopter of a protocol to provide multiple concrete types at compile time, without polluting the type definition with a bunch of type parameters. They're an interesting solution to the problem and a different kind of abstraction (abstract members) from generic type parameters (parameterization).

I do wonder if taking the Scala approach and simply supporting both type parameters and associated types for classes, structs, enums and protocols would be a better long-term approach. I haven't given it a lot of thought so there may be some major gotchas lurking. That's part of what is so exciting about a new language - watching it evolve and improve over time.

Now go forth and dazzle your colleagues and coworkers with fancy terms like Abstract Type Member. Then you too can lord it over them and render comprehension impossible.

Just stay away from sacks.

And rivers.

Not space. Space is awesome.

Swift: Associated Types--为什么协议使用关联类型而不是泛型的更多相关文章

  1. swift的关联类型

    一.术语:指定类型 typealias:用于给关联类型指定类型: 通过类型推断给关联类型指定类型太过烧脑: 二.类型指定 1.具体类型实现协议后,直接指定:作为普通的泛型类型使用,指定类型即可: 2. ...

  2. swift开发之--Protocol(协议)

    使用object-c语言的同学们肯定对协议都不陌生,但在swift中苹果将protocol这种语法发扬的更加深入和彻底. Swift中的protocol不仅能定义方法还能定义属性,配合extensio ...

  3. swift学习笔记之-协议

    //协议(Protocols) import UIKit /*协议(Protocols) 1.协议定义了一个蓝图,规定了用来实现某一特定任务或者功能的方法.属性,以及其他需要的东西 2.类.结构体或枚 ...

  4. Swift中文教程(七)--协议,扩展和泛型

    Protocols and Extensions 协议(接口)和扩展 Swift使用关键字protocol声明一个协议(接口): 类(classes),枚举(enumerations)和结构(stru ...

  5. Swift 学习笔记(面向协议编程)

    在Swift中协议不仅可以定义方法和属性,而且协议是可以扩展的,最关键的是,在协议的扩展中可以添加一些方法的默认实现,就是在协议的方法中可以实现一些逻辑,由于这个特性,Swift是可以面向协议进行编程 ...

  6. associatedtype关联类型

    associatedtype关联类型   定义一个协议时,有的时候声明一个或多个关联类型作为协议定义的一部分将会非常有用.关联类型为协议中的某个类型提供了一个占位名(或者说别名),其代表的实际类型在协 ...

  7. JSON解析关联类型发生死循环 There is a cycle in the hierarchy!

    解决办法是忽略掉关联类型的数据,使用jsonConfig进行配置,代码如下: JsonConfig jsonConfig = new JsonConfig();  //建立配置文件 jsonConfi ...

  8. Python:多态、协议和鸭子类型

    多态 问起面向对象的三大特性,几乎每个人都能对答如流:封装.继承.多态.今天我们就要来说一说 Python 中的多态. 所谓多态:就是指一个类实例的相同方法在不同情形有不同表现形式.多态机制使具有不同 ...

  9. dotnet检测类型是否为泛型

    private static string GetTableName(Type type) { //检测类型是否为泛型 if (type.GetType().IsGenericType) {//取出泛 ...

随机推荐

  1. WPF备忘录(3)如何从 Datagrid 中获得单元格的内容与 使用值转换器进行绑定数据的转换IValueConverter

    一.如何从 Datagrid 中获得单元格的内容 DataGrid 属于一种 ItemsControl, 因此,它有 Items 属性并且用ItemContainer 封装它的 items. 但是,W ...

  2. @Html.Raw() 与Newtonsoft.Json.JsonConvert.SerializeObject()

    一.后台 ViewBag.TypeList = typeList; 二.前台C# @{     var typeListFirst = ViewBag.TypeList;} 三.前台js中 var t ...

  3. Gold Rush(hnu13249)

    Gold Rush Time Limit: 2000ms, Special Time Limit:5000ms, Memory Limit:65536KB Total submit users: 15 ...

  4. 不要62(hdu2089)

    不要62 Time Limit: 1000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others) Total Submissi ...

  5. JAVA设计模式详解(四)----------单例模式

    上一章我们学习了装饰者模式,这章LZ带给大家的是单例模式. 首先单例模式是用来干嘛的?它是用来实例化一个独一无二的对象!那这有什么用处?有一些对象我们只需要一个,比如缓存,线程池等.而事实上,这类对象 ...

  6. nodejs 新建项目

    第一步: 新建工程-->选择nodejs-->creat 注意: 如果出错就使用第二步!! 第二步:建立express 模板的nodejs 点击下图的命令窗口,依次输入下面命令 命令: & ...

  7. 全局eslint不生效的处理

    react项目里能用上 eslint 的 airbnb 规范真是的,对自己的编码有很好的帮助,不经可以养成良好的代码风格,而且还能检测出 state或者变量 是否 使用过, 然而,所在团队的小伙伴们, ...

  8. 移动设备 小米2S不显示CD驱动器(H),便携设备,MTP,驱动USB Driver,MI2感叹号的解决方法

    小米2S不显示CD驱动器(H),便携设备,MTP,驱动USB Driver,MI2感叹号的解决方法 by:授客 QQ:1033553122 用户环境 操作系统:Win7 手机设备:小米2S   问题描 ...

  9. Asp.net MVC检测到有潜在危险的 Request.Form 值

    解决方法很简单,不像网上说的那么麻烦.只需一步: [ValidateInput(false)] public ActionResult Test(){ }

  10. Core Animation-1:图层树

    图层的树状结构 >巨妖有图层,洋葱也有图层,你懂吗?我们都有图层 -- 史莱克 Core Animation其实是一个令人误解的命名.你可能认为它只是用来做动画的,但实际上它是从一个叫做*Lay ...