【问题标题】:Dispatch on union types in Haskell在 Haskell 中调度联合类型
【发布时间】:2021-05-09 09:55:31
【问题描述】:

我正在尝试在 Haskell 中编写一个基于 String 或 Num 参数的多态函数。

我写了一些无法编译的伪代码:

numDigits strOrNum = 
  if isString strOrNum then length strOrNum
  else isNum strOrNum then length (show strOrNum)

numDigits 1000   -- Should return 4
numDigits "1000" -- Should return 4

注意isStringisNum 不是真正的Haskell 函数,它们只是为了演示。

来自 Lisp,strOrNum 是一个联合类型,这段代码可以运行。

我知道 Haskell 中的临时多态性需要类型类,但我不知道如何将它们组合在一起。

【问题讨论】:

  • 您可以为此使用类型类,但您很快就会遇到重叠/无法确定的实例等问题,因为您想要这样做 - 我会问自己一个问题:我真的需要需要这个吗?如果是:data MyNum = IntNum Int | StringNum String 是否足够好? (如果您为该类型执行 NumIsString 实例,您可以获得 numDigits .. ,因为您想要使用 OverloadedStrings 扩展

标签: haskell types typeclass


【解决方案1】:

将某物建模为 sum-type 还是 type-class 更好是我认为您可以随着时间的推移而建立的直觉(sum-types 通常是更好的选择)。

但是既然你提到了

我知道 Haskell 中的临时多态性需要类型类,但我不知道如何将它们组合在一起。

这是一个有希望的直观解释。

基本思路

基本上,您是说您的代码可以使用多种类型。您应该尝试制定这些类型的共同点 - 即为什么您接受这些类型但拒绝其他类型。然后,您定义一个类型类来捕获这一点并根据该类型类实现您的大部分代码。

你的例子

在你的例子中说明它。 您正在接受数字和字符串(表示数字),因为您说两者都可以表示为数字序列,并且您想要定义一个计算这些数字的函数。那么定义一个类型类来捕获被写为数字序列的能力可能是有意义的。

class IsBase10Positional t where
  digits :: t -> [Int]

然后你会为你的类型定义实例:

instance IsBase10Positional Int where
    digits n = D.digits 10 n

instance IsBase10Positional String where
    digits chars = digitToInt <$> chars

D.digits 来自http://hackage.haskell.org/package/digits-0.3.1/docs/Data-Digits.html#v:digits

digitToInt 来自https://hackage.haskell.org/package/base-4.15.0.0/docs/Data-Char.html#v:digitToInt

您的 numDigits 函数随后会在属于此类型类的所有类型上定义:

numDigits :: IsBase10Positional a => a -> Int
numDigits x = length (digits x)

结论

通常很难制定类型类应该捕获的常见行为。如果您开始在类型类本身中添加大量函数,那么您可能无法准确把握这些类型的共同点(在您的应用程序的域中)。

【讨论】:

    【解决方案2】:

    你可以这样工作:

    data MyNum
      = IntNum Int
      | StrNum String
    
    numDigits :: MyNum -> Int
    numDigits (IntNum n) = length $ show n
    numDigits (StrNum s) = length s
    
    instance Num MyNum where
      fromInteger = IntNum . fromInteger
      -- note: skipped the rest (won't work really well sorry)
    
    instance IsString MyNum where
      fromString = StrNum
    

    这是 GHCi 中的一个示例:

    > :set -XOverloadedStrings
    > numDigits "1000"
    4
    > numDigits 1000
    4
    

    这是作为 1000 工作的,因为文字被认为是任何 Num 的类型 - fromInteger 将用于将其转换为目标类型。

    当您在 GHC 中启用 OverloadedStrings 扩展时,"1000" 也是如此,这将被视为使用 fromStringIsString 实例

    我认为这只是这个问题的一个好技巧 - Num 实例显然是不完整的。

    我不得不考虑一下,但我认为这不能变成一个合法 Num 实例,因为StrNum 部分(允许以StrNum "bad" 为例)

    我的预感是你会遇到麻烦,因为你必须同时使用 StrNum ...01 的值,如果它们都相等,那么生成的环只能是 {0}(如果你甚至允许它)

    注意:这不是证据,我可能(并且可能am)错了......

    【讨论】:

      【解决方案3】:

      也许这就是你想要的?

      {-# LANGUAGE FlexibleInstances #-}
      class HasNumDigits a where
        numDigits :: a -> Int
      
      instance HasNumDigits Int where
        numDigits n = length (show n)
      
      instance HasNumDigits String where
        numDigits s = length s
      
      -- >>> numDigits (1000 :: Int)
      -- 4
      
      -- >>> numDigits "1000"
      -- 4
      
      

      另外,sum 类型通常比 union 类型更好:

      
      data StrOrNum = Num Int | Str String
      
      numDigits strOrNum = case strOrNum of
        Str str -> length strOrNum
        Num num -> length (show strOrNum)
      
      -- >>> numDigits (Num 1000)
      -- 4
      
      -- >>> numDigits (Str "1000")
      -- 4
      

      【讨论】:

        猜你喜欢
        • 2021-11-19
        • 1970-01-01
        • 1970-01-01
        • 2011-02-22
        • 2019-02-08
        • 2020-09-22
        • 1970-01-01
        • 1970-01-01
        • 2021-04-11
        相关资源
        最近更新 更多