har*_*arr 8 polymorphism haskell interface typeclass
我知道有两种方法可以将接口规范与Haskell中该接口的实现分开:
类型类,例如:
RandomGen
StdGen
记录,例如:
Network.Transport
Network.Transport.TCP
问题1:什么时候适合使用其中一种?
问题2:在Haskell中分离接口/ impl有哪些其他方法?
\n\n
问题 1 的答案非常简单:这两个选项等效于 \xe2\x80\x94 类型类,可以“脱糖”为仅数据类型。这个想法已在http://www.haskellforall.com/2012/05/scrap-your-type-classes.html中进行了描述和论证。中进行了描述和论证。
\n\n问题 2 的答案是,这两种是将接口与实现分开的唯一方法。推理是这样的:
\n\nCanFoo
类型签名()之外,还为您的接口提供一个名称(例如a -> Foo
)CanFoo
,但有更多字段);请注意,在此上下文中,记录只是具有命名字段的命名元组类型。\xe2\x80\x94 正如已经证明的那样,无论是显式还是隐式(使用类型类)传递函数,在概念上都是一回事[1]。
\n\n下面简单演示了这两种方法的等效性:
\n\ndata Foo = Foo\n\n-- using type classes\nclass CanFoo a where\n foo :: a -> Foo\n\ndoFoo :: CanFoo a => a -> IO Foo\ndoFoo a = do\n putStrLn "hello"\n return $ foo a\n\ninstance CanFoo Int where\n foo _ = Foo\n\nmain = doFoo 3\n\n-- using explicit instance passing\ndata CanFoo\' a = CanFoo\' { foo :: a -> Foo }\n\ndoFoo\' :: CanFoo\' a -> a -> IO Foo\ndoFoo\' cf a = do\n putStrLn "hello"\n return $ (foo cf) a\n\nintCanFoo = CanFoo { foo = \\_ -> Foo }\n\nmain\' = doFoo\' intCanFoo 3\n
Run Code Online (Sandbox Code Playgroud)\n\n正如您所看到的,如果您使用记录,您的“实例”将不再自动查找,而是您需要将它们显式传递给需要它们的函数。
\n\n另请注意,在简单的情况下,记录方法可以简化为仅传递函数,因为传递CanFoo { foo = \\_ -> Foo }
实际上与传递包装函数本身相同\\_ -> Foo
。
[1]
\n\n事实上,在 Scala 中,这种概念上的等价性变得显而易见,因为 Scala 中的类型类是根据类型(例如trait CanFoo[T]
)、该类型的许多值以及标记为 的该类型的函数参数进行编码的implicit
,这将使 ScalaCanFoo[Int]
在调用站点查找类型的值。
// data Foo = Foo\ncase object Foo\n\n// data CanFoo t = CanFoo { foo :: t -> Foo }\ntrait CanFoo[T] { def foo(x : T): Foo }\nobject CanFoo {\n // intCanFoo = CanFoo { foo = \\_ -> Foo }\n implicit val intCanFoo = new CanFoo[Int] { def foo(_: Int) = Foo }\n}\n\nobject MyApp {\n // doFoo :: CanFoo Int -> Int -> IO ()\n def doFoo(someInt: Int)(implicit ev : CanFoo[Int]] = {\n println("hello")\n ev.foo(someInt)\n }\n\n def main(args : List[String]) = {\n doFoo(3)\n }\n}\n
Run Code Online (Sandbox Code Playgroud)\n