我可以使用哪些技术来一致地重构代码,以消除对存在类型的依赖?通常,这些用于取消您不希望使用的类型的构造的资格,并允许在对给定类型有最少了解的情况下进行消耗(或者我的理解是这样)。
有没有人想出一种简单而一致的方法来消除对代码中这些内容的依赖,而这些代码仍然保留了一些好处?还是至少有任何一种滑入抽象的方式,使得它们可以删除而无需大量的代码改动来应对这种变更?
您可以在此处阅读有关存在性类型的更多信息(“如果您敢..”)。
我可以使用哪些技术来一致地重构代码,以消除对存在类型的依赖?通常,这些用于取消您不希望使用的类型的构造的资格,并允许在对给定类型有最少了解的情况下进行消耗(或者我的理解是这样)。
有没有人想出一种简单而一致的方法来消除对代码中这些内容的依赖,而这些代码仍然保留了一些好处?还是至少有任何一种滑入抽象的方式,使得它们可以删除而无需大量的代码改动来应对这种变更?
您可以在此处阅读有关存在性类型的更多信息(“如果您敢..”)。
Answers:
存在类型在函数式编程中并不是真正的坏习惯。我认为让您感到震惊的是,存在性最常被引用的用途之一是存在性类型类反模式,许多人确实认为这是不好的做法。
这种模式经常被淘汰,作为对如何拥有一个均实现相同类型类的异构类型元素列表的回答。例如,您可能想要一个包含Show
实例的值的列表:
{-# LANGUAGE ExistentialTypes #-}
class Shape s where
area :: s -> Double
newtype Circle = Circle { radius :: Double }
instance Shape Circle where
area (Circle r) = pi * r^2
newtype Square = Square { side :: Double }
area (Square s) = s^2
data AnyShape = forall x. Shape x => AnyShape x
instance Shape AnyShape where
area (AnyShape x) = area x
example :: [AnyShape]
example = [AnyShape (Circle 1.0), AnyShape (Square 1.0)]
像这样的代码的问题是这样的:
AnyShape
是获取其面积。AnyShape
构造函数将一种形状类型引入AnyShape
类型。事实证明,这段代码并没有真正让您得到此较短的代码所没有的任何东西:
class Shape s where
area :: s -> Double
newtype Circle = Circle { radius :: Double }
instance Shape Circle where
area (Circle r) = pi * r^2
newtype Square = Square { side :: Double }
area (Square s) = s^2
example :: [Double]
example = [area (Circle 1.0), area (Square 1.0)]
对于多方法类,通常可以通过使用“方法的记录”编码来更简单地实现相同的效果,而不是使用像这样的类型类Shape
,您可以定义一个字段为该方法的“方法”的记录Shape
类型。 ,然后编写函数将圆和正方形转换为Shape
s。
但这并不意味着存在类型是一个问题!例如,在Rust中,它们具有称为特征对象的功能,人们通常将其描述为特征上的存在类型(Rust的类型类版本)。如果存在类型类是Haskell中的反模式,这是否意味着Rust选择了错误的解决方案?没有!Haskell世界的动机是语法和便利性,而不是原则。
一种更数学的说法是指出AnyShape
上面的类型Double
是同构的 —它们之间存在“无损转换”(嗯,除了浮点精度外):
forward :: AnyShape -> Double
forward = area
backward :: Double -> AnyShape
backward x = AnyShape (Square (sqrt x))
因此严格来说,选择一个与另一个不会获得或失去任何力量。这意味着选择应该基于其他因素,例如易用性或性能。
并且请记住,存在类型在此异构列表示例之外还有其他用途,因此最好使用它们。例如,Haskell的ST
类型允许我们编写外部纯函数但内部使用内存突变操作的函数,它使用基于存在类型的技术来保证编译时的安全性。
因此,普遍的答案是没有普遍的答案。存在类型的使用只能在上下文中进行判断-答案可能会有所不同,具体取决于不同语言提供的功能和语法。
我对Haskell不太熟悉,因此我将以非学术性C#开发人员的身份回答问题的一般部分。
经过阅读后,结果是:
Java通配符类似于存在性类型:
通配符不是完全在C#中实现的:支持泛型方差,但不支持调用站点方差:
您可能并非每天都需要此功能,但是当您执行此操作时,您会感觉到它的存在(例如,必须引入额外的类型才能使事情正常运行):
基于此信息,存在类型/通配符在正确实现时很有用,它们本身没有任何问题,但是它们可能像其他语言功能一样被滥用。