【问题标题】:Enum instance for tuples in HaskellHaskell中元组的枚举实例
【发布时间】:2012-04-01 19:57:16
【问题描述】:

我想将元组 (x, y) 定义为 Enum 类的实例,因为知道 xy 都是 Enum 的实例。以下尝试:

instance (Enum x, Enum y) => Enum (x, y) where
    toEnum = y
    enumFrom x = (x, x)

只会导致错误(y 不在范围内)。我是 Haskell 的新手,有人能解释一下如何声明这样的实例吗?

【问题讨论】:

  • toEnum = y 这行应该做什么?
  • 实际上不可能从Enum xEnum y 创建一个有用的Enum (x, y)。你需要额外的上下文,比如Bounded x, Bounded y, Enum x, Enum y => Enum (x, y)
  • @sepp2k:当您编写succ (x, y) 时,您希望它有时增加x,有时增加y,并以某种方式覆盖所有可能的(x, y)。当您只获得 succ xsucc y 的公式时,您无法做到这一点。
  • 同意,仅从这种情况来看,Enum (x, y) 似乎不太可能以任何合理的方式实现——如果有的话。
  • @DietrichEpp 我可以通过使用toEnumfromEnum 轻松实现这一目标。虽然现在我想起来,这些方法使用Int,而不是Integer,这一事实会在一段时间后成为问题。

标签: haskell types


【解决方案1】:
instance (Enum x, Enum y) => Enum (x, y) where

在上述行中,xy 都是类型(类型变量)。

    toEnum = y
    enumFrom x = (x, x)

在上面两行中,xy 都是值((值)变量)。 y-as-a-value 尚未在任何地方定义,这就是它不在范围内的意思。

至于如何声明这样的实例,例如,我不确定您希望 fromEnumtoEnum 的行为方式。

【讨论】:

    【解决方案2】:

    如果你问我,这不是一个好主意,但无论如何——

    要创建类型类的实例,您需要查看签名。

    class Enum a where
      succ :: a -> a
      pred :: a -> a
      toEnum :: Int -> a
      fromEnum :: a -> Int
      enumFrom :: a -> [a]
      enumFromThen :: a -> a -> [a]
      enumFromTo :: a -> a -> [a]
      enumFromThenTo :: a -> a -> a -> [a]
    

    所以你的情况

    toEnum :: Int -> (x, y)
    

    但是toEnum = y 甚至没有被定义,因为y 只是一个类型,而不是一个值或构造函数。可能性是

    toEnum n = (toEnum 0, toEnum n)
    

    toEnum n = (toEnum n, toEnum n)
    

    toEnum n = (toEnum $ n`div`2, toEnum $ (n+1)`div`2)
    

    至于enumFrom,你的版本有签名

    enumFrom :: a -> (a,a)
    

    但我们需要

    enumFrom :: (x,y) -> [(x,y)]
    

    什么定义合适取决于toEnum是如何定义的;我的第一个建议是

    enumFrom (x,y) = [ (x,y') | y' <- enumFrom y ]
    

    阅读 Dietrich Epp 的评论

    实际上不可能从Enum xEnum y 创建一个有用的Enum (x, y)。您需要额外的上下文,例如 Bounded x, Bounded y, Enum x, Enum y =&gt; Enum (x, y)

    我想了可以真正有意义地完成它的方式。似乎确实有可能存在双射ℤ → ℤ2。我的建议:

    [ ...
    , (-3,-3), (-3,-2), (-2,-3), (-3,-1), (-1,-3), (-3,0), (0,-3), (-3,1), (1,-3), (-3,2), (2,-3), (-3,3), (3,-3)
    , (-2,3), (3,-2), (-1,3), (3,-1)
    , (-2,-2), (-2,-1), (-1,-2), (-2,0), (0,-2), (-2,1), (1,-2), (-2,2), (2,-2)
    , (-1,2), (2,-1)
    , (-1,-1), (-1,0), (0,-1), (-1,1), (1,-1)
    , (0,0)
    , (1,0), (0,1), (1,1)
    , (2,0), (0,2), (2,1), (1,2), (2,2)
    , (3,0), (0,3), (3,1), (1,3), (3,2), (2,3), (3,3)
    , ... ]
    

    请注意,这也可以简化为双射 ℕ → ℕ2,这很重要,因为一些 Enum 实例不会进入负范围,而其他实例会。

    实施:

    让我们创建一个普通的(Int,Int) 实例;很容易将其概括为您想要的。另外,我只会处理阳性病例。

    观察(0,0) 和(不包括)(k,0) 之间存在k^2 元组。所有其他元组(x,y)max x y == k 直接跟在它后面。这样,我们就可以定义fromEnum

    fromEnum (x,y) = k^2  +  2*j  +  if permuted then 1 else 0
          where k = max x y
                j = min x y
                permuted = y>x
    

    对于toEnum,我们需要找到这个函数的逆函数,即知道fromEnum -&gt; n,我们想知道参数。 k 很容易计算为floor . sqrt $ fromIntegral nj 的获得方式类似,只是用余数的div 2

    toEnum n =    let k = floor . sqrt $ fromIntegral n
                      (j, permdAdd) = (n-k^2) `divMod` 2
                      permute (x,y) | permdAdd>0  = (y,x)
                                    | otherwise    = (x,y)
                  in permute (k,j)
    

    使用fromEnumtoEnum,所有其他功能都相当简单。

    【讨论】:

    • 只有 ℤ2 → ℤ 双射是不够的,因为范围必须紧凑才能有用(我会说这是 Enum 的规则班级)。这是它如何中断的演示,因为xy 不一定具有相同的基数。 gist.github.com/2279321
    • 不同基数的实例当然是无解的,所以只会给出instance (Enum a) =&gt; Enum (a,a)。我发现toEnum 对于n&gt;3 的另一个问题是我还不明白:map (fromEnum' . toEnum') [0..] 的计算结果为[0,1,2,3,6,7,8,8,14..]。嗯……
    • @DietrichEpp 已修复,当然它必须是 n-k^2gist.github.com/2279399
    • 一般(x, y) 没有负面部分的想法:使用fromEnum 和对角化可以为元组类型构造fromEnum 实例,并使用列表操作(!!)findIndex可以创建toEnumfromEnum。它甚至不需要Eq 实例(对于findIndex),因为它可以从\x1 x2 -&gt; fromEnum x1 == fromEnum x2 合成。
    猜你喜欢
    • 1970-01-01
    • 2012-01-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多