Consider the following:
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
How could I write pattern P
?
Basically I to be able to say:
f :: B -> String
f (P 2 FortyTwo) = "The meaning of life"
That is, being able to pattern match without ever referencing the private constructors PrivateA
and PrivateB
directly outside the module where they are defined.
First, remember that
newtype
can only be used with data constructors that have a single argument, so yourA
can benewtype
butB
can't.Now, the pattern syntax you're using for
FortyTwo
is called implicitly bidirectional. That is,We use
=
and truly mean equality. It says "I can useFortyTwo
to construct anA
, and if I have anA
, I can useFortyTwo
to pattern match on it". This is the simplest form, and it's useful in "simple" cases where the arguments are nicely oriented.But the real world isn't that simple. So GHC provides us with an extended syntax called explicitly bidirectional patterns. In short, we can specify explicitly how we want an expression to behave in pattern context and how we want it to behave in expression context. The compiler doesn't (and can't) check that the two expressions make cohesive sense as a pair, so we could use it to do some nonsense like this
This defines
Nonsense
in such a way thatlet Nonsense x = Nonsense 0 in x
returns42
.But your use case sounds completely reasonable, so we can define it with explicitly bidirectional patterns.
There's one more minor piece we'll need to finish this out, and that's called view patterns. A view pattern is a pattern (hence, we use it in pattern matching) which is really just a function call in disguise. In very brief summary, the following are roughly equivalent
It really just moves the function call to the other side of the equal sign, which can be handy for writing terse code in some situations. It's also useful when defining pattern synonyms.
The first line, of course, is the type declaration. The second line says "when I see a pattern of the form
P n a
, pretend it saysPrivateB n (PrivateA -> a)
". The last line says "when I see an expression that saysP n (PrivateA a)
, construct aPrivateB n a
". This defines a Haskell function (and the function is exhaustive sinceA
only has one constructor, and we've handled it).Complete runnable example:
Try it online!