听说过duck typing吧,如果没有就先Google一下吧。
问题
假设下面不是ruby代码,duck
的类型是什么?(代码来自《冒号课堂》)
def shoutOrSwim(duck, flag)
flag ? duck.shout : duck.swim
end
你要先想想。
运行时类型检查?
我现在认为运行时类型检查对duck typing来说不是必须的(几个小时前我还在动摇)。一般情况下,我认为duck
必须是个同时支持shout
和swim
的对象。
但是有人就说:“在ruby里,只要在运行时程序员自己保证flag
为真时duck
有shout
,flag
为假的时候duck
有swim
就可以了,所以duck不需要同时有两个方法,这在静态语言是做不到的”。
这里我们不讨论method_missing
这些“故意”通过调用不存在的方法而触发的东西,静态语言可以通过依赖类型实现shoutOrSwim
,而且duck
不须同时有shout
和swim
。这里我用Haskell模拟一下依赖类型就可以实现1:
{-# LANGUAGE TypeFamilies, DataKinds, ConstraintKinds, GADTs #-}
import GHC.Exts (Constraint)
-- Ad-hoc polymorphism
class ICanShout a where
shout :: a -> String
class ICanSwim a where
swim :: a -> String
data Man = Man
instance ICanShout Man where
shout _ = "Hahaha"
data Fish = Fish
instance ICanSwim Fish where
swim _ = "Swimming"
-- Dependent type (Simulating via singleton)
type family Select (flag :: Bool) :: * -> Constraint
type instance Select True = ICanShout
type instance Select False = ICanSwim
data SBool :: Bool -> * where
STrue :: SBool True
SFalse :: SBool False
shoutOrSwim :: (Select b) a => SBool b -> a -> String
shoutOrSwim STrue duck = shout duck
shoutOrSwim SFalse duck = swim duck
因为Haskell的记录类型不是结构化类型的,所以代码的第一部分通过type class实现重载,但这不是重点。在第二部分,我们通过用type family和GADT模拟依赖类型,描述了Bool
和ICanShout
、ICanSwim
这两个约束之间的关系,所以在shoutOrSwim
的两个分支,duck
的类型约束是不同的。这也可以理解为shoutOrSwim
根据第一个参数返回不同类型的函数:
ghci> :t shoutOrSwim SFalse
shoutOrSwim SFalse :: ICanSwim a => a -> String
ghci> :t shoutOrSwim STrue
shoutOrSwim STrue :: ICanShout a => a -> String
在一个直接支持的依赖类型和结构化类型的语言,实现shoutOrSwim
会更简洁,但是思路是一样的。所以我认为结构化类型(或类似type class的重载方法)加上依赖类型就能覆盖大部分Duck typing的用法,特别是不做shoutOrSwim
这种坑爹的事,根本就不需要依赖类型。
什么是Duck typing?
Duck typing没有准确的定义,但是我认为这句话概括了它的本质:
When I see a bird that walks like a duck and swims like a duck and quacks like a duck, I call that bird a duck.
也就是说,我们更关心一个对象的行为,而不关心它到底是什么。一个对象的行为既可以通过结构化类型来隐式表达,又可以通过接口、type class显式表达,这些都不需要运行时类型检查。我甚至可以说Duck typing就是隐式的面向接口编程,和动、静态根本就关系。
其实我偷懒了,因为
shoutOrSwim
里用是SBool
,其实还需要一个把Bool
转成SBool
的过程,这是一个从动到静的过程,完成之后我们就进入静态的世界。↩