在Haskell中dynamic调度
例如,用Java编写的程序依赖于dynamic调度。 如何用Haskell等function语言来expression这样的程序? 换句话说,“dynamic调度”下的Haskellexpression方式是什么?
答案看似简单:高阶函数。 使用面向对象语言的虚拟方法的对象只不过是与当地一些国家的function相得益彰的logging。 在Haskell中,您可以直接使用函数的logging,并将本地状态存储在closures中。
更具体地说,OO对象包括:
- 指向vtable(虚拟方法表)的指针(vptr),包含对象类的虚拟方法的实现。 换句话说,一堆函数指针; functionlogging。 值得注意的是,每个函数都有一个隐含的参数,它是隐式传递的对象本身。
- 对象的数据成员(本地状态)
很多时候,整个对象和虚拟函数的大厦感觉像是一个精心制作的解决scheme,因为缺乏对closures的支持。
例如,考虑Java的Comparator
接口:
public interface Comparator<T> { int compare(T o1, T o2); // virtual (per default) }
假设你想用它来根据string的第N个字符对string列表进行sorting(假设它们足够长)。 你会定义一个类:
public class MyComparator implements Comparator<String> { private final int _n; MyComparator(int n) { _n = n; } int compare(String s1, String s2) { return s1.charAt(_n) - s2.charAt(_n); } }
然后你使用它:
Collections.sort(myList, new MyComparator(5));
在Haskell中,你可以这样做:
sortBy :: (a -> a -> Ordering) -> [a] -> [a] myComparator :: Int -> (String -> String -> Ordering) myComparator n = \s1 s2 -> (s1 !! n) `compare` (s2 !! n) -- n is implicitly stored in the closure of the function we return foo = sortBy (myComparator 5) myList
如果你对Haskell不熟悉的话,大致看起来就像一个伪Java:(我只会这样做一次)
public void <T> sortBy(List<T> list, Ordering FUNCTION(T, T) comparator) { ... } public (Ordering FUNCTION(String, String)) myComparator(int n) { return FUNCTION(String s1, String s2) { return s1[n].compare(s2[n]); } } public void foo() { sortBy(myList, myComparator(5)); }
请注意,我们没有定义任何types。 我们所用的都是function。 在这两种情况下,我们传递给sort函数的“有效载荷”是一个函数,它带有两个元素并给出它们的相对顺序。 在一种情况下,这是通过定义实现接口的types,以适当的方式实现其虚拟函数,并传递该types的对象来实现的。 在另一种情况下,我们直接传递了一个函数。 在这两种情况下,我们在传递给sort函数的东西中存储一个内部整数。 在一种情况下,这是通过在我们的types中添加一个私有数据成员来完成的,另一种情况是通过在我们的函数中简单地引用它,导致它被保留在函数的闭包中。
考虑使用事件处理程序的更复杂的小部件示例:
public class Widget { public void onMouseClick(int x, int y) { } public void onKeyPress(Key key) { } public void paint() { } ... } public class MyWidget extends Widget { private Foo _foo; private Bar _bar; MyWidget(...) { _foo = something; _bar = something; } public void onMouseClick(int x, int y) { ...do stuff with _foo and _bar... } }
在Haskell中,你可以这样做:
data Widget = Widget { onMouseClick :: Int -> Int -> IO (), onKeyPress :: Key -> IO (), paint :: IO (), ... } constructMyWidget :: ... -> IO Widget constructMyWidget = do foo <- newIORef someFoo bar <- newIORef someBar return $ Widget { onMouseClick = \xy -> do ... do stuff with foo and bar ..., onKeyPress = \key -> do ..., paint = do ... }
再次注意,在初始的Widget
,我们没有定义任何types。 我们只写了一个函数来构造函数的logging,并在closures中存储事物。 在大多数情况下,这也是定义面向对象语言的子类的唯一原因。 与前面的例子唯一的区别在于,不是一个函数有多个,而是通过简单地在接口(及其实现)中放入多个函数来编码,而在Haskell中,通过传递一个函数logging来代替一个单一的function。 (在前面的例子中,我们可以传递一个包含单个函数的logging,但是我们并不喜欢它。)
(值得注意的是,很多时候你不需要dynamic调度,如果你只是想根据types的默认sorting来sorting列表,那么你可以简单地使用sort :: Ord a => [a] -> [a]
,它使用为给定a
types定义的Ord
实例,该types是静态select的。)
基于types的dynamic调度
Java方法和上面的Haskell方法之间的区别在于,使用Java方法时,对象的行为(除了本地状态)由其types决定(或者不那么慈善,每个实现需要一个新types)。 在Haskell中,我们以我们喜欢的方式来创build函数的logging。 大多数情况下,这是一个纯粹的胜利(获得的灵活性,没有任何损失),但是假设出于某种原因我们需要Java方法。 在那种情况下,正如其他答案所提到的那样,types类和存在也是如此。
要继续我们的Widget
示例,假设我们希望Widget
实现遵循其types(为每个实现需要一个新types)。 我们定义一个types类来将types映射到它的实现:
-- the same record as before, we just gave it a different name data WidgetImpl = WidgetImpl { onMouseClick :: Int -> Int -> IO (), onKeyPress :: Key -> IO (), paint :: IO (), ... } class IsWidget a where widgetImpl :: a -> WidgetImpl data Widget = forall a. IsWidget a => Widget a sendClick :: Int -> Int -> Widget -> IO () sendClick xy (Widget a) = onMouseClick (widgetImpl a) xy data MyWidget = MyWidget { foo :: IORef Foo, bar :: IORef Bar } constructMyWidget :: ... -> IO MyWidget constructMyWidget = do foo_ <- newIORef someFoo bar_ <- newIORef someBar return $ MyWidget { foo = foo_, bar = bar_ } instance IsWidget MyWidget where widgetImpl myWidget = WidgetImpl { onMouseClick = \xy -> do ... do stuff with (foo myWidget) and (bar myWidget) ..., onKeyPress = \key -> do ..., paint = do ... }
我们只有一个类才能获得函数的logging,然后我们不得不单独地将这些函数分离出来,这有些尴尬。 我只是用这种方式来说明types类的不同方面:它们也只是函数的logging(我们在下面使用)和一些魔术,其中编译器根据推断的types插入适当的logging(我们在上面使用,并继续使用下面)。 让我们简化一下:
class IsWidget a where onMouseClick :: Int -> Int -> a -> IO () onKeyPress :: Key -> a -> IO () paint :: a -> IO () ... instance IsWidget MyWidget where onMouseClick xy myWidget = ... do stuff with (foo myWidget) and (bar myWidget) ... onKeyPress key myWidget = ... paint myWidget = ... sendClick :: Int -> Int -> Widget -> IO () sendClick xy (Widget a) = onMouseClick xya -- the rest is unchanged from above
这种风格经常被来自OO语言的人所采用,因为它比OO语言更为熟悉,更接近于一对一的映射。 但是对于大多数目的而言,这只是比第一部分描述的方法更复杂,更不灵活。 原因是,如果关于各种Widget的唯一重要的事情是他们实现Widget函数的方式,那么在为这些types创buildtypes,接口的实例,然后通过将它们放入一个存在的包装:直接传递函数更简单。
我可以想到的一个好处是,虽然Haskell没有子types,但它确实具有“子类化”(可能更好地称为子接口或子约束)。 比如你可以这样做:
class IsWidget a => IsWidgetExtra a where ...additional methods to implement...
然后用任何你有IsWidgetExtra
types,你也可以无缝地使用IsWidget
的方法。 基于logging的方法的唯一select是logging内logging,这涉及到内部logging的一些手动打包和解包。 但是,如果你想要明确地模仿面向对象语言的深层次的层次结构,那么这样做只会是有利的,而反过来,你只有在你想让自己生活困难时才会这样做。 (还要注意的是,Haskell没有任何内置的方法来从IsWidget
dynamic地IsWidget
到IsWidgetExtra
,但是IsWidgetExtra
却没有)
(基于logging的方法的优点是什么?除了每次你想要做一件新事物时不需要定义一个新types,logging都是简单的价值层面的东西,而且值比操作types更容易操作。例如,编写一个以Widget
为参数的函数,并根据它创build一个新的Widget
,其中有些事情是不同的,其他的保持不变,这就像C ++中的模板参数的子类化一样,只是不那么容易混淆。 )
词汇表
-
高阶函数:将其他函数作为参数的函数(或将其作为结果返回)
-
logging:结构(类与公共数据成员,没有别的)。 也被称为字典。
-
闭包:函数式语言(和许多其他语言)允许你定义本地函数(函数内的函数,lambdaexpression式),这些函数引用定义站点上的范围内的东西(例如,外函数的参数)被保留在周围,而是在函数的“closures”中。 另外,如果你有一个像
plus
这样的函数,并且返回一个int,你可以将它应用于一个参数,例如5
,结果将是一个函数,它接受一个int并返回一个int,将5加到它 – 在这种情况下,5
也被存储在结果函数的闭包中。 (在其他情况下,“封闭”有时也被用来表示“封闭的function”)。 -
Type类:与OO语言中的类不同。 有点像一个界面,但也有很大的不同。 看到这里 。
编辑29-11-14:虽然我认为这个答案的核心仍然是基本正确的(Haskell中的HOFs与OOP中的虚拟方法相对应),但是我的价值判断从我写下来以来已经有了一些微妙的变化。 特别是,我现在认为,哈斯克尔和面向对象的方法都不是严格的“更基础”的方法。 看到这个reddit评论 。
多么经常你不需要dynamic调度,只是多态性,这是令人惊讶的。
例如,如果您要编写一个将列表中的所有数据进行sorting的函数,那么您希望它是多态的。 (也就是说,你不需要手工重新实现这个函数,这样做会很糟糕)。但是实际上你并不需要任何dynamic的东西, 你可以在编译时知道你想要sorting的列表或列表中的实际内容。 所以在这种情况下你根本不需要运行时types的查询。
在Haskell中,如果你只是想要移动东西,而不需要知道或关心它是什么types,那么可以使用所谓的“参数多态”,这大致类似于Javagenerics或C ++模板。 如果您需要能够对数据应用一个函数(例如,对数据进行sorting,您需要进行命令比较),则可以将该函数作为parameter passing。 或者,Haskell有一个有点像Java接口的东西,你可以说“这个sorting函数接受实现这个接口的任何types的数据”。
到目前为止,根本没有dynamic调度,只有静态的。 还要注意的是,由于您可以将函数作为parameter passing,因此可以手动进行“调度”。
如果你真的需要实际的dynamic分派,你可以使用“存在types”,或者你可以使用Data.Dynamic
库和类似的技巧。
即席多态是通过types类来完成的。 更多类似OOP的DD用存在types来模拟。
也许你需要ADT加模式匹配?
data Animal = Dog {dogName :: String} | Cat {catName :: String} | Unicorn say :: Animal -> String say (Dog {dogName = name}) = "Woof Woof, my name is " ++ name say (Cat {catName = name}) = "Meow meow, my name is " ++ name say Unicorn = "Unicorns do not talk"