如何在Haskell中创建异构列表?(最初是Java)

Sim*_*mon 5 haskell typeclass

如何将以下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();
    }
}
Run Code Online (Sandbox Code Playgroud)

哈斯克尔

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
Run Code Online (Sandbox Code Playgroud)

lef*_*out 18

简单回答:不要!Haskell不是一种面向对象语言,假装它并不是很好,只是试图将继承模式转换为类型类和ADT的混合.

List<Foo>的Java Foo a => [a]与Haskell中的完全不同:这样的签名实际上意味着forall a . Foo a => [a].这a基本上是函数的额外参数,即它可以从外部选择在此Foo使用的特定实例.

在Java中恰恰相反:您根本无法控制列表中的类型,只知道它们实现了Foo接口.在Haskell中,我们将其称为存在类型,并且通常避免它,因为它是愚蠢的.好的,你不同意 - 对不起,你错了!
......不,严重的是,如果你有这样一个存在列表,你可以永远做的唯一的事情1是执行bar动作.好吧,那么为什么不立刻把这个动作放在列表中呢!IO()动作就像其他任何东西一样(它们不是函数;无论如何,它们也可以放在列表中).我会写你的程序

xs :: [IO ()]
xs = [bar Bar1, bar Bar2]
Run Code Online (Sandbox Code Playgroud)


也就是说,如果你绝对坚持你也可以在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
Run Code Online (Sandbox Code Playgroud)

因为这已经变得非常咆哮:我确实认为OO风格对于某些应用来说比Haskell的功能风格更方便.并且存在确实有它们的有效用例(但是,就像chunksOf 50,我宁愿把它们写成GADT).只是,对于许多问题,Haskell允许更简洁,更强大,更通用,但在许多方面比简单的解决方案更简单,而不是"如果你有一把锤子......"继承你在OO编程中使用,所以在使用存在之前你应该对Haskell的"原生"特征有一个正确的感觉.


1是的,我知道你也可以用Java做"类型安全动态演员"等.在Haskell中,有Typeable这类东西的类.但是,如果采取这种方式,你也可以立即使用动态语言.

  • 使用存在并不总是错误的,它通常是错误的. (5认同)

Hel*_*hne 9

您的翻译有一个重要的缺点.而在您的Java版本中,您可以轻松添加一个Bar3也支持该Foo界面,但如果不触及BarHaskell版本中的类型,则无法轻松实现相同的功能.所以这不是你要找的版本.

在某种程度上,您正在寻找异构列表.其他 问题已涵盖这方面.

你真正想要的是完全摆脱类型类的需要.而是具有表示以下行为的数据类型Foo:

data Foo = Foo { bar :: IO () }
Run Code Online (Sandbox Code Playgroud)

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


use*_*391 7

这是可能的,但可能不可取.像"存在类型"这样的语言扩展允许动态多态.

存在类型的想法如下:

data Foo = Foo a
Run Code Online (Sandbox Code Playgroud)

请注意,类型变量"a"不会出现在ADT声明的左侧.可能实现动态多态列表和映射函数的简单示例:

{-# LANGUAGE UnicodeSyntax, Rank2Types, GADTs, ConstraintKinds #-}

import Data.Constraint

-- List datatype:
data PList ? where
   Nil  ? PList ?
   (:*) ? ? 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
Run Code Online (Sandbox Code Playgroud)

输出:

["\"Truly polymorphic list \"","'a'","1","(2,2)"]
Run Code Online (Sandbox Code Playgroud)

使用此技术的另一个例子:

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' :* (1 ? Int) :* Nil
    in do 
        print $ pmap name l2
        print $ pmap show l2
Run Code Online (Sandbox Code Playgroud)

输出:

["I'm Char and my value is 'a'","I'm Int and my value is 1"]
["'a'","1"]
Run Code Online (Sandbox Code Playgroud)