You can use a view type and view patterns to do what you want:
module ThingModule (Thing, ThingView(..), view) where
data Thing = Foo Thing | Bar Int
data ThingView = FooV Thing | BarV Int
view :: Thing -> ThingView
view (Foo x) = FooV x
view (Bar y) = BarV y
Note that ThingView
is not a recursive data type: all the value constructors refer back to Thing
. So now you can export the value constructors of ThingView
and keep Thing
abstract.
Use like this:
{-# LANGUAGE ViewPatterns #-}
module Main where
import ThingModule
doSomethingWithThing :: Thing -> Int
doSomethingWithThing(view -> FooV x) = doSomethingWithThing x
doSomethingWithThing(view -> BarV y) = y
The arrow notation stuff is GHC’s View Patterns. Note that it requires a language pragma.
Of course you’re not required to use view patterns, you can just do all the desugaring by hand:
doSomethingWithThing :: Thing -> Int
doSomethingWithThing = doIt . view
where doIt (FooV x) = doSomethingWithThing x
doIt (BarV y) = y
More
Actually we can do a little bit better: There is no reason to duplicate all the value constructors for both Thing
and ThingView
module ThingModule (ThingView(..), Thing, view) where
newtype Thing = T {view :: ThingView Thing}
data ThingView a = Foo a | Bar Int
Continue useing it the same way as before, but now the pattern matches can use Foo
and Bar
.
{-# LANGUAGE ViewPatterns #-}
module Main where
import ThingModule
doSomethingWithThing :: Thing -> Int
doSomethingWithThing(view -> Foo x) = doSomethingWithThing x
doSomethingWithThing(view -> Bar y) = y