多态返回类型,接口,回调?

Seb*_*ach 4 haskell

假设Goo是我的类型类,通常声称它是C++,Java或C#等语言中的等价接口:

class Goo goo where ham :: goo -> String

data Hoo = Hoo
instance Goo Hoo where ham _ = "Hoo!"
                       mustard _ = "Oh oh."

data Yoo = Yoo
instance Goo Yoo where ham _ = "Yoo!"
                       mustard _ = "Whew"
Run Code Online (Sandbox Code Playgroud)

但我不能回复Goo:

paak :: (Goo goo) => String -> goo
paak g = (Yoo)

-- Could not deduce (goo ~ Yoo)
-- from the context (Goo goo)
--  bound by the type signature for paak :: Goo goo => String -> goo
--  at frob.hs:13:1-14
--  `goo' is a rigid type variable bound by
--        the type signature for paak :: Goo goo => String -> goo
--        at frob.hs:13:1
-- In the expression: (Yoo)
-- In an equation for `paak': paak g = (Yoo)
Run Code Online (Sandbox Code Playgroud)

我找到了这个启发性的陈述,它解释了为什么:

该类型paak :: (Goo goo) => String -> goo并不意味着该函数可能返回Goo它想要的任何内容.这意味着该函数将返回Goo用户想要的任何一个.

(从sepp2k的答案音译到这里)

但后来,我怎么可能退回或商店的东西,满足Goo约束条件,但也可以是Hoo,Yoo,Moo,Boo或任何其他Goo

我只是在自己的编程背景中纠缠不清,需要完全不同的思考,比如求助于类似C的界面:

data WhewIamAGoo = WhewIamAGoo {
    ham' :: String
    mustard' :: String
}

paak :: String -> WhewIamAGoo
paak g = let yoo = Yoo 
         in WhewIamAGoo { ham' = ham yoo
                          mustard' = mustard ham
                        }
Run Code Online (Sandbox Code Playgroud)

但这似乎很尴尬.

在我的具体情况下,我想这样使用Goo:

let x = someGoo ....
in ham x ++ mustard x
Run Code Online (Sandbox Code Playgroud)

即呼叫者不应该知道所有的Yoos和诸如此类的东西.


编辑:澄清:我正在寻找Haskell程序员在这种情况下的方式.你如何以惯用的方式处理它?

ham*_*mar 8

有两种解决这个问题的方法我认为是惯用的Haskell:

  1. 代数数据类型

    data Goo = Hoo | Yoo
    
    ham Hoo = "Hoo!"
    ham Yoo = "Yoo!"
    
    mustard Hoo = "Oh oh."
    mustard Yoo = "Whew"
    
    Run Code Online (Sandbox Code Playgroud)

    Pro:易于添加新操作
    Con:添加新的"类型"可能需要修改许多现有功能

  2. 记录支持的操作

    data Goo = Goo { ham :: String, mustard :: String }
    
    hoo = Goo { ham = "Hoo!", mustard = "Oh oh." }
    yoo = Goo { ham = "Yoo!", mustard = "Whew" }
    
    Run Code Online (Sandbox Code Playgroud)

    Pro:轻松添加新的"类型"
    Con:添加新操作可能需要修改许多现有功能

你当然可以混合搭配这些.一旦你习惯于考虑函数,数据和组合而不是接口,实现和继承,这些在大多数情况下都是足够好的.

类型类是为重载而设计的.使用它们来模拟Haskell中面向对象的编程通常是一个错误.


Ben*_*Ben 5

类型类似于Java风格的接口,但是你并没有像使用接口那样使用它们,所以它不是学习它们的好方法.

接口一种类型(因为OO语言具有子类型,因此其他类型可以是接口的子类型,这是您完成任何操作的方式).所有类型在Haskell中都是不相交的,因此类型类不是类型.它是一类型(实例声明是您声明集合成员的位置).试着用这种方式来思考它们.它使得类型签名的正确读取更加自然(String -> a意味着"获取String并返回您想要的任何类型的值",并且SomeTypeClass a => String -> a意味着"获取String并返回您想要的任何类型的值,SomeTypeClass").

现在你无法以你想要的方式做你想做的事,但我不确定你为什么需要按你想要的方式去做.为什么不能paak只有这种类型String -> Yoo

你说你正在尝试做类似的事情:

let x = someGoo ....
in ham x ++ mustard x
Run Code Online (Sandbox Code Playgroud)

如果someGoo ...paak "dummy string",那么x将是类型Yoo.但是,Yoo是的一员Goo,那么你可以调用Goo类似的方法ham,并mustard在其上.如果您稍后更改paak为以不同Goo类型返回值,则编译器将告诉您使用任何Yoo特定功能的所有位置,并且愉快地接受未更改的任何已调用paak但仍然仅使用该Goo功能的位置.

为什么你需要输入"某些未知类型的Goo"?从根本上说,呼叫者paak 不在任何类型的操作Goo,他们只在paak实际返回的操作,这是一个Yoo.

您有混凝土的类型,它可以调用来自类型类,其中的具体类型是其成员对这些具体类型的功能以及功能操作某些功能.或者你有一些函数可以在任何类型上运行,这些类型是某个类型类的成员,在这种情况下,你可以调用的所有函数都适用于类型类中的任何类型.