如何在Haskell中创建一个异构列表?(原本是Java)

6

如何将以下Java实现转换为Haskell?

主要目的是创建一个列表,其中包含特定接口的各种子元素。
我试图在下面制作Haskell版本,但未能达到我的目的。 关键在于xs的类型为[Bar]而不是Foo a => [a]

这意味着Haskell不能完成此操作,我应该用另一种方式思考吗?

Java

interface Foo {
    void bar ();
}

public class Bar1 implements Foo {
    @Override
    public void bar() {
        System.out.println("I am bar 1 class");
    }   
}

public class Bar2 implements Foo {
    @Override
    public void bar() {
        System.out.println("I am bar 2 class");
    }   
}

public static void main(String[] args) {
    // The major purpose here is having a list 
    // that contains elements which are sub-type of "Foo"
    List<Foo> ys = new ArrayList<Foo>();

    Foo e1 = new Bar1();
    Foo e2 = new Bar2();

    ys.add(e1);
    ys.add(e2);

    for (Foo foo : ys) {
        foo.bar();
    }
}

Haskell

class Foo a where
  bar :: a -> IO ()

data Bar = Bar1 | Bar2

instance Foo Bar where
  bar Bar1 = print "I am Bar1"
  bar Bar2 = print "I am Bar2"

--xs :: Foo a => [a]
xs :: [Bar]
xs = [Bar1, Bar2]

main :: IO ()
main = mapM_ bar xs

1
你在 Haskell 中的实现看起来很好。我甚至可以说适当的翻译是:bar1 = "我是 Bar1"; bar2 = "我是 Bar2"; xs = [bar1, bar2]; main = mapM_ putStrLn xs - kosmikus
其实这里的关键是拥有一个实现特定接口的元素列表。 - Simon
1
是的,但在这种情况下,该接口是String类型或者可能是IO String类型。 - kosmikus
4个回答

19

简短的回答是:不要! Haskell 不是面向对象的语言,假装它是然后试图将继承模式转换为类型类和 ADT 的混合形式是没有多大用处的。

在 Java 中,你的 List<Foo> 和在 Haskell 中的 Foo a => [a] 有相当大的不同:这样的签名实际上意味着 forall a . Foo a => [a]。也就是说,a 是函数的额外参数,即可以从外部选择使用哪个特定的 Foo 实例。

而在 Java 中则完全相反:你根本无法控制列表中有哪些类型,只知道它们实现了 Foo 接口。在 Haskell 中,我们称之为“存在类型”,通常会避免使用,因为它很愚蠢。好吧,你不同意 - 抱歉,你错了!
...不,认真地说,如果你有这样一个存在的列表,你唯一能做的事情1就是执行 bar 操作。那么,为什么不直接将该操作放入列表中呢IO() 操作就像任何其他值一样(它们不是函数;无论如何,这些函数也可以放在列表中)。我会这样编写你的程序:

xs :: [IO ()]
xs = [bar Bar1, bar Bar2]


话虽如此,如果你坚持的话,在Haskell中也可以使用存在列表:

{-# LANGUAGE ExistentialQuantification #-}

data AFoo = forall a. Foo a => AFoo a

xs :: [AFoo]
xs = [AFoo Bar1, AFoo Bar2]

main = mapM_ (\(AFoo f) -> bar f) xs

由于这已经成为一个相当长的抱怨: 我承认OO风格对于某些应用程序而言比Haskell的函数式风格更方便。存在类型确实有其有效的用例(尽管,就像chunksOf 50一样,我更喜欢将它们编写成GADTs)。只是,在许多问题上,Haskell允许使用比OO编程中“如果你所有拥有的是锤子...”继承更简洁、强大、通用但在许多方面更简单的解决方案,因此,在使用存在类型之前,您应该对Haskell的“本地”特性有一个适当的感觉。


1是的,我知道你也可以在Java中做“类型安全的动态转换”等等。在Haskell中,有Typeable类来处理这种情况。但如果您采用这种方法,最好直接使用动态语言。


1
GADTs 是一种更漂亮的实现存在量化的方式,但是像你说的那样,IO() 列表或代表你想要的所有内容的数据类型才是最好的。 - not my job
5
使用存在句并不总是错误的,但通常是错误的。 - augustss
1
编写 xs = [bar Bar1, bar Bar2] 的唯一“痛苦”之处在于,当我有许多Bar(Bar3、Bar4等)时,我需要多次输入 bar 函数的类型。我在想是否可以用另一种方式设计,或者在这种情况下必须使用存在类型? - Simon
这可能会少一些抱怨。 - David Given
@DavidGiven:我从未否认过。 - leftaroundabout
显示剩余3条评论

9
你的翻译存在一个重要缺陷。在你的Java版本中,你可以轻松添加一个支持Foo接口的Bar3,但在Haskell版本中,如果不触及Bar类型,很难实现同样的功能。所以这不是你要找的版本。
某种程度上,你正在寻找异构列表。其他 问题 已经涵盖了这个方面。
然而,你真正想要的是摆脱对类型类的需求。相反,有一个数据类型代表Foo的行为:
data Foo = Foo { bar :: IO () }

然后,您可以将满足 Foo 接口的对象列表构建为 [Foo]

1
值得注意的是,这与存在类型 data Boxy = forall a. Classy a => Boxy a 非常相似,因为类型类最终会转换为记录。 - daniel gratzer

7
这是可行的,但可能不是理想的。有一种语言扩展叫做“存在类型”,它允许动态多态性。
存在类型的思想如下:
data Foo = Foo a

请注意,类型变量"a"在ADT声明的左侧不出现。下面是动态多态列表和映射函数可能实现的简单示例:
{-# LANGUAGE UnicodeSyntax, Rank2Types, GADTs, ConstraintKinds #-}

import Data.Constraint

-- List datatype:
data PList α where
   NilPList α
   (:*) ∷ α a ⇒ a → PList α → PList α

infixr 6 :*

-- Polymorphic map:
pmap ∷ (∀ a. α a ⇒ a → b) → PList α → [b]
pmap _ Nil      = []
pmap f (a :* t) = f a : pmap f t

main = let
        -- Declare list of arbitrary typed values with overloaded instance Show:
        l ∷ PList Show
        l = "Truly polymorphic list " :* 'a' :* 1 :* (2, 2) :* Nil
    in do
        -- Map show to list:
        print $ pmap show l

输出:

["\"Truly polymorphic list \"","'a'","1","(2,2)"]

使用此技巧的另一个示例:

class Show a ⇒ Named a where
    name ∷ a → String

instance Named Int where
    name a = "I'm Int and my value is " ++ show a

instance Named Char where
    name a = "I'm Char and my value is " ++ show a

 main = let
        -- Declare list of arbitrary typed values with overloaded instance Named:
        l2 :: PList Named
        l2 = 'a' :* (1Int) :* Nil
    in do 
        print $ pmap name l2
        print $ pmap show l2

输出:

["I'm Char and my value is 'a'","I'm Int and my value is 1"]
["'a'","1"]

这将是另一个实现。谢谢。Foo被命名为“幽灵类型”吗? - Simon

2

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接