类型类和记录作为接口

6
我正在尝试在运行时连接函数(必须这样做),这可能涉及函数输入和输出中的类型类约束,这让我很苦恼。
在像Java这样的语言中,这很简单。例如f1 :: Int -> Num, f2 :: Num -> Num,现在我们可以调用f2 . f1。如果Num是一个Java风格的接口,那么这不会有问题。但类型类不像接口那样工作。
类型类允许您避免在数据类型之间进行转换。我们可以随意地来回转换并摆脱类型类。但问题是我们无缘无故地创建了大量的对象。让我们尽量避免这种情况。
我开始尝试创建一个元组(a, Api a),其中Api是对将对a操作的函数进行记录的记录。我猜这与类型类的工作方式非常相似?但然后你会遇到具体类型问题,我想到了存在性。但是然后我意识到(a, Api a)应该能够完全隐藏a,因为没有人关心它,然后Api变成了纯数据类型记录,而不是函数。
所以我不得不想知道……是懒惰解决了这个问题吗?
module Main where

--data Api a = Api { f1 :: a -> Int, f2 :: a -> String }
data Api = Api { f1 :: Int, f2 :: String }

data MyData = MyData Int String

myf1 (MyData x y) = x
myf2 (MyData x y) = y
myApi x = Api (myf1 x) (myf2 x)

from :: Int -> Api
from x = myApi $ MyData x "string"

to :: Api -> String
to api = f2 api

main = print $ to . from $ 5

那么,它是否足够智能(或可能)意识到根本不需要创建一个 Api 值,因为我们只需要调用 MyData 值上的 myf2 函数?

所以,“等同于” Java 接口的东西并不是类型类,而是记录或数据类型?惰性提供了接口的“轻量级”特性?


2
Typeclasses并不是类或接口。它们更接近于C++模板。 - Fred Foo
1个回答

12
但后来我意识到,(a, Api a)应该能够完全隐藏a,因为没有人关心它,然后Api变成了一个纯粹的数据类型记录,而不是函数。
没错!当普通数据类型可以使用时,使用类似存在类型的方式被称为existential typeclass antipattern(也请参见the FAQ)。然而,它与惰性并没有直接关系;在严格语言中,你同样可以将每个字段表示为() -> Result。当然,这样做使用起来不会像现在这么好用。
类型类的优点在于其隐式、类型导向的解析:你可以直接使用操作,就像它们在特定类型上是单态的一样,这样就可以正常工作,而不必为想要实现操作的每种类型都想出一个单独的名称。举个类型类有价值的例子,想象一下如果Num被实现为记录;你将不得不把每种类型的实现记录到处传递。更糟糕的是,没有地方可以放置fromInteger :: (Num a) => Integer -> a,因为它在结果中是多态的,并且根本不取任何类型a的值!
另一个例子是像Ord这样的东西。你不能将Ord表示为记录,因为Ord的任何实例都必须对此类型类转换消除的值进行操作。将Ord作为类型类还使我们编写可以适用于任何可以排序的值类型的通用代码,这非常有价值,在这方面,类型类的使用确实与OOP接口有某些相似之处。
然而,当没有真正相关的值可谈论时,或者它们全都来自于“外部”,只是作为内部状态(例如存在性)时,类型类只会添加不必要的样板代码,并且远远不如“一级”函数和数据类型那样重要。在Haskell中,函数和数据类型是真正的抽象单元;类型类只是方便之物。
那么,它是否足够聪明(或者可以),以意识到根本不需要创建Api值,因为我们所需的只是对MyData值上的myf2调用?
GHC可能会创建中间数据类型,但您不应该过于担心;存在体还携带着类型类实例字典,就像您的Api类型一样。这个记录界面的优势并不真正在于性能,而在于简洁性、清晰性和组合性(将记录的所有字段转换为“修改”实现非常容易,但无法有一个函数来转换类型类实例)。

3
Type classes 在 Haskell 中是一种重要的抽象形式,因为它们允许进行一般化处理,否则将不可能。举个简单的例子:sort 可以对 任何 可以一致实现 Ord 类型类的类型进行排序。定义 sort 只需使用 Ord 所提供的基本假设即可。 - Dan Burton
@DanBurton:当然,我在回答中也有类似的话,但在编辑过程中不小心丢失了:) 我已经添加了一些新的词汇来代替它们。 - ehird
哦,闪亮!另外,我刚刚注意到,在 lambda 演算中缺乏类型类,我确实做了你说过的事情:“在每个地方传递每种类型的实现记录”。https://dev59.com/7l_Va4cB1Zd3GeqPTXON#8936209 - Dan Burton

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