在Haskell动态调度(Dynamic dispatch in Haskell)

2019-07-02 19:44发布

写在例如Java程序依赖于动态调度很多。 如何这种方案在功能性语言,如Haskell的表达? 换句话说什么是表达“动态调度”下的想法哈斯克尔方式?

Answer 1:

答案看似简单:高阶函数。 在面向对象语言虚方法的对象无非是功能在一起的荣耀纪录,一些地方的状态的更多。 在Haskell你可以直接使用的功能的记录,并存储在他们的封锁本地状态。

更具体地,一个面向对象的对象包括:

  • 的指针(vptr的)到一个虚函数表(虚拟方法表),其中包含该对象的类的虚拟方法的实现。 换句话说,一束函数指针; 功能的记录。 值得注意的是每个函数有一个隐藏的参数,其是与隐式传递对象本身。
  • 对象的数据成员(本地状态)

很多时候对象和虚函数的整个大厦感觉就像在缺乏闭包支持一个精心制作的解决方法。

例如,考虑Java的Comparator界面:

public interface Comparator<T> {
    int compare(T o1, T o2); // virtual (per default)
}

而假设你想用它来进行排序基于字符串第N个字符的字符串列表(假定他们是足够长的时间)。 你会定义一个类:

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

如果你不熟悉哈斯克尔,这里是它是如何将大致看在一种伪爪哇:(我只打算做一次)

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));
}

需要注意的是,我们并没有定义任何类型。 我们使用的都是功能。 在这两种情况下,我们传递给排序功能“有效载荷”是一个函数,它有两个元素,并给出了它们的相对排序。 在一个情况下,这是通过定义一个类型实现一个接口,在适当的方式执行其虚函数,并通过该类型的对象来实现; 在其他情况下,我们只是直接传递的功能。 在这两种情况下,我们存储在我们传给排序功能的东西的内部整数。 在一种情况下,这是通过简单地参照它在我们的功能,使其在功能的关闭被保留加入私有数据成员到我们的类型,在其他完成。

考虑事件处理小部件的更详细的例子:

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 = \x y -> do
            ... do stuff with foo and bar ...,
        onKeyPress = \key -> do ...,
        paint = do ...
    }

再次注意初始后Widget ,我们并没有定义任何类型。 我们只写了一个函数来构造函数和店里的东西记录在其封闭。 其中,大部分的时间,也是一种面向对象的语言来定义一个子类的唯一原因。 从我们前面的例子唯一的区别是,而不是一个功能有多个,这在Java的情况下,通过简单地把一个以上的功能,在界面(和它的实现)编码,并在哈斯克尔通过传递功能的记录,而不是一个单一的功能。 (我们可以通过了包含在前面的例子在单一功能的记录,但我们不喜欢它。)

(值得一提的地方,很多时候你并不需要动态分配。如果你只是想基于该类型的默认排序排序列表,那么只需在使用sort :: Ord a => [a] -> [a]其使用Ord对于给定的定义的实例a 。类型,其被静态地选择的)

基于类型的动态调度

在Java方法和上述的Haskell方法之间的一个区别是与Java方法中,一个对象的(除了其本地状态)的行为是由它的类型来确定(或更少宽厚,每个实现需要新的类型)。 在Haskell我们正在做我们的功能,但是记录我们喜欢的方式。 这其中大部分是纯粹的胜利时(灵活性上涨,没有丢失),但假设由于某种原因,我们希望它在Java的方式。 在这种情况下去,正如其他的答案中提到的方式,是类型类和existentials。

继续我们Widget例子,假设我们希望有一个执行Widget从它的类型遵循(要求每个实现一个新的类型)。 我们定义一个类型映射类类型的实现:

-- 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 x y (Widget a) = onMouseClick (widgetImpl a) x y

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 = \x y -> do
                ... do stuff with (foo myWidget) and (bar myWidget) ...,
            onKeyPress = \key -> do ...,
            paint = do ...
        }

这是一个有点尴尬的是,我们有一个类只有拿到的功能,这是我们接下来要采取的职能进行单独的记录。 我只做到了这种方式来说明类型的类分开方面:有一些神奇的地方编译器插入基于推断出的类型(相应的记录我们在上面使用起来也就是功能只是荣耀的记录(我们将在下面使用) ,并继续使用下图)。 让我们简化:

class IsWidget a where
    onMouseClick :: Int -> Int -> a -> IO ()
    onKeyPress   :: Key -> a -> IO ()
    paint        :: a -> IO ()
    ...

instance IsWidget MyWidget where
    onMouseClick x y myWidget = ... do stuff with (foo myWidget) and (bar myWidget) ...
    onKeyPress key myWidget = ...
    paint myWidget = ...

sendClick :: Int -> Int -> Widget -> IO ()
sendClick x y (Widget a) = onMouseClick x y a

-- the rest is unchanged from above

这种风格往往被人们从面向对象的语言来采用,因为它更熟悉,从路面向对象的语言做它更接近于一个换一个映射。 但对于大多数的目的,它只是更复杂,比在第一部分中描述的方法灵活。 其原因是,如果对各种小部件的唯一显著的是他们实现小部件功能的方式,那么就在做类型,这些类型的接口的实例,然后抽象掉底层的类型再小点,通过把它们放在一个存在的包装:它更简单,只是周围直接传递的功能。

一个优点,我想到的是,虽然Haskell没有亚型,它确实有“子类”(可能是更好的被称为子接口或子约束)。 例如,你可以这样做:

class IsWidget a => IsWidgetExtra a where
    ...additional methods to implement...

然后用您拥有任何类型IsWidgetExtra ,你也可以使用的方法IsWidget无缝连接。 与基于记录的方法中,唯一的选择是让记录中之记录,其中涉及一些手册的内记录包裹和 - 解缠。 但是,如果你想明确地模拟一个面向对象的语言,这反过来,如果你想难为自己,你就只能做深的类层次结构这只会是有利的。 (另请注意,哈斯克尔没有任何内置的方式来动态的向下转换从IsWidgetIsWidgetExtra ,但是仍然有ifcxt )

(怎么样基于记录的方法的优势是什么?除了不必你想要做一个新的东西,每次定义一个新的类型,记录是简单的价值层面的东西,和值是更容易比类型的操纵。你可以,例如,编写一个函数,需要一个Widget作为参数,并进行新Widget此基础上,对有些事情不同,其它保持不变,这是有点像在C ++模板参数继承,只是少混乱。 )

词汇表

  • 高阶函数:一个使用其他功能的参数(或将它们作为结果)的函数

  • 实录:结构(类公共数据成员,没有别的)。 也称为字典。

  • 关闭:函数式语言(和许多其他人)允许您定义的本地函数的定义网站提及的事情的范围内(函数,lambda表达式功能)(例如,外部函数的参数),你通常会想到还没要保持周围,但是,在功能上的“关闭”。 或者,如果你有一个像功能plus采用两个整数并返回一个int,你可以申请它只是一个说法,说5 ,结果将是一个函数,它接受一个int并返回一个int,将5它-在这种情况下, 5也存储在所产生的功能的关闭。 (在其他上下文中“封闭”有时也用于表示“与闭合的功能”)。

  • Type类: 一样的面向对象语言中的类。 有点像一个接口,但也有很大不同。 看到这里 。

编辑29-11-14:虽然我觉得这个答案的内核本质上仍然是正确的(在Haskell HOFs对应于OOP虚拟方法),我的价值判断已经长大了一些细微的差别,因为我写的。 特别是,我现在觉得,无论是Haskell的也不OOP的做法是严格比其他“更根本的”。 见这个reddit的评论 。



Answer 2:

这是令人惊讶多久你实际上并不需要进行动态调度,只是多态性。

举例来说,如果你要编写在一个列表排序的所有数据的功能,你希望它是多态的。 (也就是说,你不想重新实现这个功能对于每一个类型,由专人这将是糟糕的。),但你实际上并不需要任何动态 ; 你知道在编译时究竟是在列表或列出要排序。 所以,你实际上并不需要在这种情况下运行时类型的查找都没有。

在Haskell,如果你只是想走动的东西,你不需要知道或关心它是什么类型,你可以用所谓的“参数多态”,这大概是像Java泛型或C ++模板。 如果您需要能够给一个函数应用到数据(例如,你需要订购的比较数据进行排序),你可以通过函数来​​做到这一点作为一个参数。 另外,Haskell有一件事,这是一个有点像Java接口,你可以说“这个排序函数接受任何类型的实现此接口的数据”。

到目前为止,还没有动态调度可言,只有静态的。 还要注意的是,因为你可以传递函数作为参数,你可以在“调度”手工做手工。

如果你真的需要实际的动态调度,可以用“生存型”,或者您可以使用Data.Dynamic库,以及类似的技巧。



Answer 3:

特设多态是通过做类型类 。 更多OOP般的DD是模拟与存在的类型 。



Answer 4:

也许你需要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"


文章来源: Dynamic dispatch in Haskell