我正在尝试在运行时(我必须这样做)连接函数,这可能涉及函数的输入和输出中的类型类约束.
在像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
Run Code Online (Sandbox Code Playgroud)
那么,它是否足够智能(或可能)意识到它根本不需要创建Api值,因为我们需要的是在MyData值上调用myf2?
所以Java接口的"等价"不是某人告诉我的类型类,而是记录或数据类型?那种懒惰提供了界面的"轻量级"?
ehi*_*ird 12
但后来我意识到(a,Api a)应该能够完全隐藏a,因为没有人关心,然后Api变成了数据类型的简单记录,而不是函数.
究竟!使用像普通数据类型那样的存在性称为存在类型类反模式(参见常见问题解答).然而,它与懒惰并没有直接关系; 你可以像() -> Result
严格的语言一样轻松地代表每个领域.当然,使用起来并不是那么好.
类型类的优点在于隐式的,类型导向的解析:你可以直接使用操作,就像它们在你正在使用的特定类型上是单态的一样,并且它工作正常,而不必提出一个要为其实现操作的每种类型的单独名称.有关为什么类型类有价值的示例,请想象Num是否作为记录实现; 你必须在每个地方传递每种类型的实施记录.更糟糕的是,没有地方可以放置fromInteger :: (Num a) => Integer -> a
,因为它在结果中是多态的,并且根本不采用任何类型的值a
!
另一个例子是Ord.您不能将Ord表示为记录,因为Ord的任何实例都必须对此类型到记录转换所消除的值进行操作.将Ord作为类型类也可以让我们编写对可以排序的任何类型的值都是通用的代码,这确实是非常有价值的,并且在这方面,类型类的使用在某种程度上肯定类似于OOP接口.
然而,当没有真正的相关值可言,或者它们都来自"外部"只是作为内部状态(如存在主义)时,类型类只是增加了不必要的样板,而不是"一流".函数和数据类型是Haskell中真正的抽象单元; 类型类只是方便.
那么,它是否足够智能(或可能)意识到它根本不需要创建Api值,因为我们需要的是在MyData值上调用myf2?
GHC可能会创建中间数据类型,但你不必过于担心; 存在主义也带有类型类实例字典,就像你的Api类型一样.这个记录界面的优点不是真正的性能,而是简单性,清晰度和可组合性(很容易转换记录的所有字段以产生"修改"的实现 - 但你不能拥有一个转换类型的函数 - 类实例).