Haskell:如何将接口与实现分离
我知道在Haskell中将接口规范与接口实现分离的两种方法:Haskell:如何将接口与实现分离,haskell,interface,polymorphism,typeclass,Haskell,Interface,Polymorphism,Typeclass,我知道在Haskell中将接口规范与接口实现分离的两种方法: 类型类别,例如: 接口: 执行:StdGen 记录,例如: 接口: impl: 问题1:什么时候使用一个或另一个合适 问题2:在Haskell中,还有什么其他方法可以分离接口/impl?问题1的答案很简单:这两个选项是等效的-类型类可以“删除”为数据类型。这一想法已被描述,并被论证支持 问题2的答案是,这两种方法是分离接口和实现的唯一方法。理由如下: 最终目标是以某种方式传递函数——这是因为在Haskell中,除了作为函数之
- 接口:
- 执行:
StdGen
- 接口:
- impl:
问题2:在Haskell中,还有什么其他方法可以分离接口/impl?问题1的答案很简单:这两个选项是等效的-类型类可以“删除”为数据类型。这一想法已被描述,并被论证支持
问题2的答案是,这两种方法是分离接口和实现的唯一方法。理由如下:
a->Foo
)之外,还要给接口一个名称(例如,CanFoo
)CanFoo
,但有更多字段);请注意,在本上下文中,记录只是一个有命名字段的命名元组类型以下是两种方法如何等效的简单演示:
data Foo=Foo
--使用类型类
CanFoo a班在哪里
foo::a->foo
doFoo::CanFoo a=>a->IO Foo
doFoo a=do
putStrLn“你好”
返回$foo a
实例CanFoo Int在哪里
foo=foo
main=doFoo 3
--使用显式实例传递
数据CanFoo'a=CanFoo'{foo::a->foo}
doFoo'::CanFoo'a->a->IO Foo
doFoo'cf a=do
putStrLn“你好”
返回$(foo cf)a
intCanFoo=CanFoo{foo=\\\\\->foo}
main'=doFoo'intCanFoo 3
如您所见,如果使用记录,您的“实例”将不再自动查找,而是需要显式地将它们传递给需要它们的函数
还要注意的是,在一个简单的例子中,record方法可以简化为只传递函数,因为传递CanFoo{foo=\\\\\\\\->foo}
实际上与传递包装函数\\\\\\->foo
本身是一样的
[1] 事实上,在Scala中,当Scala中的类型类按照类型(例如
trait CanFoo[T]
)、该类型的许多值以及标记为implicit
的该类型的函数参数进行编码时,这种概念上的等价性就变得很明显,这将导致Scala查找类型CanFoo[Int]的值
在呼叫站点
//数据Foo=Foo
案例对象Foo
//数据CanFoo t=CanFoo{foo::t->foo}
trait CanFoo[T]{def foo(x:T):foo}
对象CanFoo{
//intCanFoo=CanFoo{foo=\\\\\->foo}
隐式val intCanFoo=new CanFoo[Int]{def foo(u:Int)=foo}
}
对象MyApp{
//doFoo::CanFoo Int->Int->IO()
def doFoo(someInt:Int)(隐式ev:CanFoo[Int]={
println(“你好”)
ev.foo(someInt)
}
def main(参数:列表[字符串])={
多福(3)
}
}
当您希望每种类型都有一个唯一的实现,或者您想按其特定类型来标记实现,或者您只是需要隐式实例解析的便利性时,请使用类型类。否则,请使用记录。但这只是我的观点……我认为至少第1部分是基于观点的(有些人几乎在任何情况下都是一方胜于另一方的顽固支持者……)。您可能会发现这一点很有用:类型类不是用于与规范分离的接口。它们用于引入上下文相关的重载符号。类型类作为模块的一个问题是,在类型类可见的范围内,所有实例都可见。@user2407038您的建议遵循Gabriel在nex提供的链接中所说的内容不要评论。谢谢。@Sibi-谢谢你的链接。Gabriel总是有很好的建议。我个人觉得typeclass示例比另一个更容易理解。我不明白为什么很多人反对他们。至于记录方法(如doFoo'
),你可以启用记录通配符并编写doFoo'CanFoo'{..}a=do{…;return(fooa)}
。这使得函数在语法上更加出色,即使记录字段是中缀二进制函数(即data Num a=Num{(+)::a->a->a}
)