Haskell: 有没有更好的方法来编写具有相同 RHS 的 case 语句?

Haskell: Is there a better way to write case statements with the same RHS?

示例:

data A =
  A B D
  | Aa B C
  | Ag B X X
  | Ae B R Q
  | Ax X

getB a = case a of 
    (A b _)         -> b
    (Aa b _)        -> b
    (Ag b _ _)      -> b
    (Ae b _ _)      -> b
    (Ax _)          -> somethingElse

在 Haskell 中,给定一个数据类型,其中许多构造函数具有相同的参数类型,是否有更好的方法来 return 这个参数。或者有没有更好的方法来编写上面显示的 case 语句以减少重复?

函数级别的模式匹配将有助于提高可读性,但由于这些都是不同的构造函数,因此无法一次匹配多个构造函数(据我所知)。

getB (A b _)    = b
getB (Aa b _)   = b
getB (Ag b _ _) = b
getB (Ae b _ _) = b
getB (Ax _)     = somethingElse

如果A有一个数据实例,你可以写

import Data.Data
mgetB :: A -> Maybe B
mgetB = gmapQi 0 cast

然后根据该函数定义 getB

ML 中可用的称为 "or patterns" 的功能对此有很大帮助。这样的功能是 requested for GHC five years ago, but no one appears to have taken on the task of specifying the details and actually implementing it. However, there is a package offering a way to do something like this with Template Haskell, as explained in Or-patterns in Haskell

在某些时候你必须说出你的 B 是如何包含在 A 中的,所以你也可以用一般的方式一劳永逸地做到这一点。

bOrX a = case a of
    (A b _)         -> B' b
    (Aa b _)        -> B' b
    (Ag b _ _)      -> B' b
    (Ae b _ _)      -> B' b
    (Ax x)          -> X' x

随后您可以用很少的代码一次匹配所有 B

getB a = case bOrX a of
  B' b -> b
  X' _ -> somethingElse

anotherFunctionWithBandX a = case bOrX a of
  B' b -> f b
  X' x -> g x

您可以使用 Prism and the (^?) operator from the lens package 让这更容易一些:

{-# LANGUAGE TemplateHaskell #-}

import Control.Lens

-- ...

data A =
  A { _b :: B, _d :: D }
  | Aa { _b :: B, _c :: C }
  | Ag { _b :: B, _x1 :: X, _x2 :: X }
  | Ae { _b :: B, _r :: R, _q :: Q }
  | Ax { _x1 :: X }

makeLenses ''A

getB :: A -> B
getB a = case a ^? b of
  Just theB -> theB
  Nothing   -> somethingElse

调用模板 Haskell 函数 makeLenses 处理所有样板文件。

lens 如果您只是为此使用它,可能会有点严重依赖,但这是需要考虑的事情(特别是如果您已经在使用 lens/考虑使用 lens).

您可以使用记录语法来解决这个问题:

data A =
  A {fieldB :: B, fieldC :: C} |
  Aa {fieldB :: B, fieldX1 :: X, fieldX2 :: X} |
  Ag {fieldB :: B, fieldR :: R, fieldQ :: Q} |
  Ax {fieldX :: X}

getB a = case a of
  Ax -> somethingElse
  _  -> fieldB a

关键是给B.

类型的所有字段赋予相同的名称