A “Type Erasure” Pattern that Works in Swift:类型域的转换

新视角:通过函数式编程的范畴理论来看待这个问题会更容易理解;

在低层类型无法很好表达的类型,可以将其转化为高阶类型进行表示。

将协议的实现类型转化为monad类型;

解决将具有关联类型的协议当作类型的

swift所谓的类型擦除是指通过协议的泛型化、继承、封装,将协议类型抹除的过程;

泛化的最终目的:

将实现了同一协议,并且关联类型一样的类型作为同一类型来看待。

接口类型最终目的:

1、接口类型用实例类型初始化后的行为和实例类型的行为一致;

2、实现了协议的可类型化,和实例类型的封装;

3、隐藏了底层的实现细节,合并了类型功能和封装功能;

let testGo: [_AnyCupBase<Coffee>] = [_AnyCupBox(CoffeeMug()), _AnyCupBox(CoffeeCup())]

终极限制:关联类型的一致性必须的到满足;

涉及到的元素:

关联类型的可指定类型;

协议的实例类型;

协议的泛化类型:协议的可类型化;用于做类型使用;用于确保关联类型的一致性;用于将容器类型从关联类型的功能中解放出来

容器类型、泛化类型的继承:1、用于包装协议的实例类型;2、用于给协议的泛化类型指定类型;

接口类型:1、接口层面只包含泛型化的关联类型;2、将协议的泛化类型作为类型来引用容器类型;3、初始化容器类型,赋值给协议的泛化类型类型;

协议的泛化是协议可类型化的基础;

消息的传递通过类似与代理机转发机制来实现。

Introduction

If you have ever tried to use Swift protocols with associated types as types, you probably had (just like me) a very rough first (and possibly second) ride.  Things that should be intuitively doable are just not and the compiler gives infuriating error messages.  It comes down to the fact that the language is not yet “finished” (and, as a result, protocols are not really usable as types — yet).  The good news is that problems have workarounds and one such workaround is “type erasure” (not the Java compiler feature, something else that goes by the same name).  Apple uses the type erasure pattern every time you read Any<Something> and cleverer people than myself described it well (more information in the references at the end).  This is my attempt at understanding the pattern.

So what’s the problem?

Imagine that you have a protocol Cup  that describes a generic cup, with an associated type Content  that describes the cup’s content.  Imagine that this protocol has some methods and some properties.  For fun (and for realism) let’s make these methods mutating  and returning and instance of Self  (the specific instance of Cup we are dealing with).

Let’s define some concrete types for Content .

Let’s also add some concrete types for the protocol Cup . A cup of tea and a cup of coffee perhaps.  Honouring the spirit of Swift, I’ll make them value types ( struct ) but it would work in the same way for a class , without any further adjustment.

We can nicely create different instances of cups with different contents.

And here are the problems that I invariably face when I start thinking of Cup  as a type (it’s not a type! It’s a protocol!).

(1) Collection of protocol types

(2) Returning a protocol type in a function

(3) Taking a protocol type as a parameter  to a function

(4) Using a protocol type as a type for class or struct properties

The error message

The error message, in all cases above, is always the same: protocol ‘Cup’ can only be used as a generic constraint because it has Self or associated type requirements.  In other words, if a protocol has associated types you cannot use such protocol as a type. It makes sense, the Swift compiler will not know what exact protocol type you are dealing with unless the associated type is specified.  A nice mnemonic way to put could be:

Swift does not like multidimensional type uncertainty, but one dimensional type uncertainty is OK.

If we can somehow fix the associated type Content , we can make it work.  There are individual workarounds that work for problems (2) to (4) but they have different limitations: we are not going to discuss them here.  We are going to present a more general solution that takes care of all four problems.

The Solution

The architecture for the solution requires a few steps and a lot of patience to follow.  I will try my best to be clear.  Here we go.

(1) Base class mocking the protocol

First let’s define a base class that describes the protocol Cup .  The associated type Content  is now turned into a generic parameter for the class.  The class cannot be used directly because it cannot be initialised.  Furthermore, every property and every method throw a fatal error upon access or call: they must be overridden, all of them.  Why do we need this base class?  It’s a trick.  As we will see later, we need to be able to store a subclass of it but we cannot refer directly to an instance of such subclass.

(2) A box containing a concrete instance of the protocol

Let’s define a box, where the concrete type for protocol Cup  is stored: such box will inherit from our previously define abstract class.  By inheriting from _AnyBase , it also follows the Cup  protocol.  The generic parameter ConcreteCup  also follows the protocol Cup .  All methods and properties of ConcreteCup  are wrapped by this class. Note that when referring to this class we need to specify a concrete type for Cup . For example: _AnyCupBox<TeaCup> .  That is not what we want.  When referring to its super class instead, we only need to specify a concrete type for its Content .  For example: _AnyCupBase<Tea> .  This should shed some light on why we need a base abstract class to inherit from and this trick lays at the heart of the technique.

(3) The pseudo-protocol

And finally we can define the public class that we are going to use.

See how this works!

Conclusion

While I am still not very comfortable with this pattern (a lot of boilerplate, difficult to fully grasp), I will give it a go as it seems very general.   You can also refer to two other excellent blog posts by Realm and by The Big Nerd Ranch basically outlining the same architecture and source of inspiration for this writing.

https://blog.jayway.com/2017/05/12/a-type-erasure-pattern-that-works-in-swift/

原文地址:https://www.cnblogs.com/feng9exe/p/10524303.html