在枚举类型上创建函数

13

我刚开始学习 Haskell。我觉得我已经掌握了基础知识,但我想确保我真的在强制自己以函数式思维方式来思考。

data Dir = Right | Left | Front | Back | Up | Down deriving (Show, Eq, Enum)
inv Right = Left
inv Front = Back
inv Up = Down
无论如何,我试图做的就是创建一个函数,用于映射每个“Dir”及其相反/倒数。我知道我可以轻松地继续写下去还有3行,但我不禁想知道是否有更好的方法。我尝试添加:
inv a = b where inv b = a

但是显然你不能这样做。所以我的问题是:是否有一种方法可以生成其余的反函数,或者创建此函数的更好方法?

非常感谢。

5个回答

18
如果UpDown之间的配对关系是一个重要的特征,那么也许这个知识应该在类型中反映出来。
data Axis = UpDown | LeftRight | FrontBack
data Sign = Positive | Negative
data Dir = Dir Axis Sign

inv 现在变得简单了。


这样更好。准确地捕捉类型中的核心概念(维度和方向)。 - Don Stewart
有道理。在“符号”上找到“inv Positive”的倒数比在所有“Dir”上找到“inv Right”的倒数更好。因此,干净地进行“反转”的唯一方法是使您要反转的内容变得非常小... - rcbuchanan
1
@rcb451,你没有理解重点。 Sign 的数据构造函数较少是次要的(虽然肯定有益)。 关键观察是LeftRight之间的密切关系应该反映在它们的类型中,同样地,例如LeftTop之间的远离关系也是如此。 类型系统可帮助处理不变量:使用更多类型,可以表达更精确的不变量。 - Lambdageek

2

您是否有一个与此函数对应的索引的封闭形式解?如果有,您可以使用Enum派生来简化事情。例如,

import Prelude hiding (Either(..))

data Dir = Right
         | Front
         | Up

         | Left
         | Back
         | Down
     deriving (Show, Eq, Ord, Enum)

inv :: Dir -> Dir
inv x = toEnum ((3 + fromEnum x) `mod` 6)

请注意,这取决于构造函数的顺序!
*Main> inv Left
Right
*Main> inv Right
Left
*Main> inv Back
Front
*Main> inv Up
Down

这段代码非常类似于C语言,利用了构造函数的顺序,并且不太像Haskell。一种折中的方法是使用更多类型,定义构造函数和它们的镜像之间的映射,避免使用算术运算。

import Prelude hiding (Either(..))

data Dir = A NormalDir
         | B MirrorDir
     deriving Show

data NormalDir = Right | Front | Up
     deriving (Show, Eq, Ord, Enum)

data MirrorDir = Left  | Back  | Down     
     deriving (Show, Eq, Ord, Enum)

inv :: Dir -> Dir
inv (A n) = B (toEnum (fromEnum n))
inv (B n) = A (toEnum (fromEnum n))

例如。
*Main> inv (A Right)
B Left
*Main> inv (B Down)
A Up

所以至少我们不必做算术运算。并且类型区分镜像案例。但是,这非常不符合Haskell的风格。枚举案例也是完全可以的!毕竟其他人在某个时候也会阅读你的代码...


2
pairs = ps ++ map swap ps where
   ps = [(Right, Left), (Front, Back), (Up, Down)]
   swap (a, b) = (b, a)

inv a = fromJust $ lookup a pairs    

[编辑]

或者这样怎么样?

inv a = head $ delete a $ head $ dropWhile (a `notElem`)
        [[Right,Left],[Front,Back],[Up,Down]]

1

我不认为我会推荐这样做,但在我看来,简单的答案是添加这个:

inv x = fromJust $ find ((==x) . inv) [Right, Front, Up]

我无法抵制改变Landei的答案以适应我的风格;这是一个类似的、稍微更推荐的解决方案,不需要其他定义:
inv a = fromJust $ do pair <- find (a `elem`) invList
                      find (/= a) pair
  where invList = [[Right, Left], [Up, Down], [Front, Back]]

它使用了 Maybe Monad。


嗯?错误?那不如声明 inv x=inv x - comonad
1
我的意思是在他已经为inv Rightinv Frontinv Up编写的定义之后添加这个。自己看看它是否有效 - Dan Burton

1

了解枚举从零开始是很好的。

助记符:fmap fromEnum [False,True] == [0,1]


import Data.Bits(xor)

-- Enum:       0   1          2   3       4   5
data Dir = Right | Left | Front | Back | Up | Down
           deriving (Read,Show,Eq,Ord,Enum,Bounded)

inv :: Dir -> Dir
inv = toEnum . xor 1 . fromEnum

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接