【问题标题】:Take action based on a type parameter's typeclass?根据类型参数的类型类采取行动?
【发布时间】:2023-01-20 19:36:42
【问题描述】:

我怀疑我有一个基本的误解需要纠正,所以将从一般概念开始,然后放大导致我以这种方式思考的特定实例。

一般来说,是否可以编写一个函数,其类型签名具有参数化类型,并根据类型参数属于类型类的位置采取不同的操作?

例如,如果你有

data MyTree a Node { val :: a, left :: Maybe MyTree, right :: Maybe MyTree }

PrettyPrint :: MyTree a -> String
PrettyPrint (Show a => ...) t = show (val t)
PrettyPrint                 t = show "?"

其中PrettyPrint $ Node 'x' Nothing Nothing 将打印xPrettyPrint $ Node id Nothing Nothing 将打印?

导致我来到这里的是一些我正在处理复杂的参数化数据类型(例如MyTree)的实例,在我需要进行一些调试之前,它进展顺利。当我插入 trace 语句时,我发现自己希望在使用测试 (Showable) 数据时派生 Show 我的数据类型参数。但我理解一个人应该 never add typeclass constraints in data declarations 正如 LYAH 所说的非常有启发性。这是有道理的,我不应该仅仅因为我想调试它而人为地限制我的数据类型。

所以我最终将类型类约束添加到我正在调试的代码中,但很快发现它们像病毒一样传播。每个调用我正在调试的低级函数的函数也需要添加约束,直到我基本上只是暂时将约束添加到每个函数以便我可以获得足够的测试覆盖率。现在我的测试代码正在污染我正在尝试开发的代码并使其偏离轨道。

我认为最好改为模式匹配并将约束排除在签名之外,或者使用多态性并定义我的函数的调试版本,或者以其他方式将我的调试跟踪包装在一个条件中,该条件仅在类型参数是实例时触发来自Show。但是在我的曲折中,我找不到一种方法来做到这一点或一个明智的选择。

【问题讨论】:

  • 不,您不能将任何内容基于不是类成员的类型。为什么不说data MyTree a = ... deriving Show
  • 众所周知,调试多态函数可能会很痛苦,因为缺少对类型参数的 Show 约束。常见的方法是打印任何可以打印的内容,或者尽可能添加 Show 约束,良好的编码习惯见鬼去吧。

标签: haskell


【解决方案1】:

一个好的心态是,从编译器的角度来看,每一种可能是每个类的实例。当一个类型不是Show的实例时,这只是意味着还没有找到instance,可能还没有写,但并不是说它不存在。

方法一

...因此,试图根据一个类型是否是一个类的实例来做出决定确实存在根本性的缺陷。然而,你能够要做的是编写一个明确区分这种区别的类。对于Show,这可能只是

class MaybeShow a where
  showIfPossible :: a -> Maybe a

一个通用的版本是将以下内容包裹在 Show 类中:

{-# LANGUAGE GADTs #-}

data ShowDict a where
  ShowDict :: Show a => ShowDict a

class MaybeShow a where
  maybeShowDict :: Maybe (ShowDict a)

然后

{-# LANGUAGE TypeApplications, ScopedTypeVariables, UnicodeSyntax #-}

showIfPossible :: ∀ a . MaybeShow a => Maybe (a -> String)
showIfPossible = fmap (ShowDict -> show) (maybeShowDict @a)

无论哪种方式,这仍然意味着您有 MaybeShow 约束污染了您的代码库——这在某种意义上优于 Show,因为它不排除不可显示的类型,但在某种意义上也更糟,因为它需要为所有类型添加实例您需要使用的类型(即使它们已经有一个 Show 实例)。

方法二

您似乎已经考虑过将约束添加到数据类型。虽然旧语法 data Show a => MyTree a = ... 确实不应该使用,但它可以将实例封装在data 中。事实上,我已经在上面用ShowDict 做到了。与其通过 MaybeShow 约束隐式获取,您还可以选择将其添加到您的数据类型中:

data MyTree a = Node { val :: a
                     , showable :: Maybe (ShowDict a)
                     , left :: Maybe (MyTree a)
                     , right :: Maybe (MyTree a) }

当然,如果您使用 Show 实例只是为了显示此特定节点的 val,那么您也可以将结果放在那里:

data MyTree a = Node { val :: a
                     , valDescription :: Maybe (String)
                     , left :: Maybe (MyTree a)
                     , right :: Maybe (MyTree a) }

当然,现在您正在以不同的方式污染您的代码库:每个函数产生MyTree 值需要获取 Show 实例,或者决定不能。不过,这可能影响较小,尤其是如果 MyTree 只是一个示例,并且您有更多仅适用于抽象容器的功能。

方法三

至少对于调试的特定情况,以及其他一些用例,最好使用单独的方法打开和关闭 Show 要求。最暴力的方法是一个很好的旧预处理器标志:

{-# LANGUAGE CPP #-}

#define DEBUGMODE
          -- (This could be controlled from your Cabal file)

prettyPrint :: 
#ifdef DEBUGMODE
        Show a =>
#endif
                MyTree a -> String
#ifdef DEBUGMODE
prettyPrint (Show a => ...) t = show (val t)
#else
prettyPrint                 t = show "?"
#endif

更精致一点的是约束同义词和合适的调试功能,可以在一个地方换掉:

{-# LANGUAGE ConstraintKinds #-}

#ifdef DEBUGMODE
type DebugShow a = Show a
debugShow :: DebugShow a => a -> String
debugShow = show
#else
type DebugShow a = ()
debugShow :: DebugShow a => a -> String
debugShow _ = "?"
#else

PrettyPrint :: DebugShow a => MyTree a -> String
PrettyPrint t = debugShow (val t)

后者再次用约束污染代码库,但您永远不需要为这些编写任何新实例。

CPP 是一个非常生硬的工具,因为它需要在编译期间全局选择是否需要 Show。但它也可以通过专用的类型级标志进行更严格的限制:

{-# LANGUAGE TypeFamilies, DataKinds #-}

data DebugMode = NoDebug | DebugShowRequired

type family DebugShow mode a where
  DebugShow 'NoDebug a = ()
  DebugShow 'DebugShowRequired a = Show a

class KnownDebugMode (m :: DebugMode) where
  debugShow :: DebugShow m a => a -> String

instance KnownDebugMode NoDebug where
  debugShow _ = "?"
instance KnownDebugMode DebugShowRequired where
  debugShow = show

{-# LANGUAGE AllowAmbiguousTypes #-}

prettyPrint :: ∀ m a . DebugShow m a => MyTree a -> String
prettyPrint t = debugShow (val t)

这看起来很像方法 1,但好处是您不需要为单个 a 类型创建任何新实例。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-10-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-09-23
    • 1970-01-01
    • 1970-01-01
    • 2022-01-20
    相关资源
    最近更新 更多