【问题标题】:How do typeclasses and modules interact?类型类和模块如何交互?
【发布时间】:2011-03-14 18:17:32
【问题描述】:

为了掌握更好的类型类(几乎从零开始),我尝试使用面积计算对二维形状进行建模,如下所示:

module TwoDShapes where

class TwoDShape s where
    area :: s -> Float

data Circle = Circle Float deriving Show
aCircle radius | radius < 0 = error "circle radius must be non-negative"
               | otherwise  = Circle radius
instance TwoDShape Circle where
    area (Circle radius) = pi * radius * radius

data Ellipse = Ellipse Float Float deriving Show
anEllipse axis_a axis_b | axis_a < 0 || axis_b < 0 = error "ellipse axis length must be non-negative"
                        | otherwise                = Ellipse axis_a axis_b
instance TwoDShape Ellipse where         
    area (Ellipse axis_a axis_b) = pi * axis_a * axis_b

其他类型的形状依此类推。

这很好,但我突然想试试这个:

module TwoDShapes  where

class TwoDShape s where
    area :: s -> Float

data TwoDShapeParams = TwoDShapeParams Float Float Float deriving Show

instance TwoDShape TwoDShapeParams where
    area (TwoDShapeParams length_a length_b constant) = foldl (*) 1 [length_a, length_b, constant]

aCircle radius | radius < 0 = error "circle radius must be non-negative"
               | otherwise  = TwoDShapeParams radius radius pi

anEllipse axis_a axis_b | axis_a < 0 || axis_b < 0 = error "ellipse axis length must be non-negative"
                        | otherwise                = TwoDShapeParams axis_a axis_b pi

等等。这也很好。为了隐藏信息,我将模块声明更改为如下所示:

module TwoDShapes (TwoDShape, area, aCircle, anEllipse, aRectangle, aTriangle)

令我惊讶的是,这 1) 有效,2) 在 ghci aCircle 中计算为 TwoDShapeParams 1.0 1.0 3.1415927,这是真的,但我不明白 TwoDShapeParams 类型如何在模块外可见。我不确定我在期待什么,但不是这个。

我真正想要的是类型类、它的方法和“智能构造函数”在模块外部可见,除此之外别无其他。可以吗?

【问题讨论】:

    标签: haskell module visibility typeclass


    【解决方案1】:

    虽然TwoDShapes 的表示是隐藏的,但您已经为它派生了一个Show 实例,它允许将TwoDShapes 类型的任意值转换为String,所以这是信息泄露。一个真正抽象的类型不应该定义一个Show 实例,或者实际上是一个Data 实例,它类似地公开有关表示的信息。只要String 独立于表示形式,有一种方法可以将您的类型转换为String 是很好的(参见Data.Map.MapData.Array.ArrayShow 实例以获取很好的示例)。

    请注意,模块系统正在完成它的工作:您仍然不能在定义它的模块之外引用 TwoDShapes 构造函数。

    【讨论】:

    • 令我惊讶的是(我没有在这台计算机上安装 Haskell 编译器,所以无法检查会发生什么)是 aCircle 在非导出类型TwoDShapeParams(不是其值构造函数)出现在其类型中。
    • 你写了module TwoDShapes (TwoDShape, ...,它导出了type TwoDShape,但不是它的表示。但是,即使您没有导出类型,您仍然可以导出类型提到 TwoDShape 的函数,只是无法在其他任何地方引用 TwoDShape。 Haskell 模块系统控制标识符的可见性,仅此而已。
    • 我不是问题的作者 :)
    • 对不起!无论如何,希望解释就足够了。
    • 啊哈。没有意识到默认情况下 Show 会生成一个包含表达式的字符串,该表达式应该评估为显示的值。在这种情况下,它当然会做我所看到的。我查看但不理解 Data.Map.Map 中的实例,但我确实弄清楚了: instance Show TwoDShapeParams where showsPrec ds = showString $ printf "a shape with bounding box side lengths %f %f" (first s) (second s) where first (TwoDSapeParams abk) = a second (TwoDSapeParams abk) = b PS:你们怎么可以让代码在 cmets 中以 tt 字体出现而我不能?
    【解决方案2】:

    如果你在 ghci 提示符中看到*TwoDShapes,它可以访问模块中的所有内容:

    http://www.haskell.org/ghc/docs/6.12.1/html/users_guide/interactive-evaluation.html

    新的提示符是 *Main,这表明我们在 Main 模块的顶层上下文中键入表达式。我们刚刚加载的 Main 模块顶层范围内的所有内容也在提示符范围内(可能包括 Prelude,只要 Main 没有显式隐藏它)。

    语法 *module 表明它是模块的完整顶级范围,它有助于在提示符处键入的表达式的范围。没有 *,只有模块的导出是可见的。

    尝试在没有* 的情况下加载TwoDShapes,并检查aCircle 的类型。

    【讨论】:

      猜你喜欢
      • 2010-12-19
      • 1970-01-01
      • 2018-03-16
      • 1970-01-01
      • 2014-03-01
      • 2010-11-15
      • 2017-02-01
      • 1970-01-01
      • 2019-03-05
      相关资源
      最近更新 更多