编写模式同义词以隐藏构造函数

Writing a pattern synonym to hide a constructor

考虑以下几点:

module MyModule (
  A(FortyTwo), -- Note we don't expose PrivateA
  B(P) -- Nor PrivateB
) where

pattern FortyTwo = A 42

newtype A = PrivateA Int
data B = PrivateB Int Int

pattern P :: Int -> A -> B

我怎么写模式P

基本上我会说:

f :: B -> String
f (P 2 FortyTwo) = "The meaning of life"

也就是说,无需直接在定义它们的模块外引用私有构造函数 PrivateAPrivateB 即可进行模式匹配。

首先,请记住 newtype 只能与具有单个参数的数据构造函数一起使用,因此您的 A 可以是 newtypeB 不能.

data B = PrivateB Int Int

现在,您用于 FortyTwo 的模式语法称为隐式双向。也就是说,

pattern FortyTwo :: A
pattern FortyTwo = PrivateA 42

我们使用 = 真正意味着平等。它说“我可以使用 FortyTwo 构造一个 A,如果我有一个 A,我可以使用 FortyTwo 对其进行模式匹配”。这是最简单的形式,在参数方向正确的“简单”情况下很有用。

但现实世界并没有那么简单。因此 GHC 为我们提供了一个 extended syntax 称为 显式双向模式 。简而言之,我们可以明确指定我们希望表达式在模式上下文中的行为方式以及我们希望它在表达式上下文中的行为方式。编译器不会(也不能)检查这两个表达式作为一对是否具有内聚意义,所以我们可以用它来做一些像这样的废话

pattern Nonsense :: Int -> Int
pattern Nonsense n <- n where
  Nonsense _ = 42

这定义了 Nonsense 这样 let Nonsense x = Nonsense 0 in x returns 42.

但是您的用例听起来完全合理,因此我们可以使用明确的双向模式来定义它。

我们还需要完成另一件小事,那就是 view patterns。视图模式是一种模式(因此,我们在模式匹配中使用它)实际上只是伪装的函数调用。在非常简短的总结中,以下内容大致相同

let y = f x in y
let (f -> y) = x in y

它实际上只是将函数调用移到了等号的另一边,这在某些情况下可以方便地编写简洁的代码。它在定义模式同义词时也很有用。

pattern P :: Int -> A -> B
pattern P n a <- PrivateB n (PrivateA -> a) where
  P n (PrivateA a) = PrivateB n a

第一行当然是类型声明。第二行说“当我看到 P n a 形式的模式时,假装它说 PrivateB n (PrivateA -> a)”。最后一行说“当我看到一个表示 P n (PrivateA a) 的表达式时,构造一个 PrivateB n a”。这定义了一个 Haskell 函数(并且该函数是详尽的,因为 A 只有一个构造函数,我们已经处理了它)。

完整的可运行示例:

{-# LANGUAGE PatternSynonyms, ViewPatterns #-}

module Main where

pattern FortyTwo :: A
pattern FortyTwo = PrivateA 42

newtype A = PrivateA Int
data B = PrivateB Int Int

pattern P :: Int -> A -> B
pattern P n a <- PrivateB n (PrivateA -> a) where
  P n (PrivateA a) = PrivateB n a

f :: B -> String
f (P 2 FortyTwo) = "The meaning of life"
f _ = "Nope :("

main :: IO ()
main = do
  putStrLn $ f (PrivateB 2 42)
  putStrLn $ f (PrivateB 2 43)

Try it online!