5
votes

I'd like to write a function that takes both

  • a value constructor for a certain algebraic data type, and
  • an actual value of that same type,

and determines whether the given value is "made from" the given constructor. Pattern matching seems like a natural fit for this, but the pattern to match against would have to be a function parameter instead of a hard-coded constructor name.

The code below is what I've tried, but GHC reports a parse error on the line indicated.

Is there a way to accomplish this?

data FooBar = Foo Int | Bar String

-- Imagine that these are useful functions.
processInt :: Int -> String
processInt = show
processString :: String -> String
processString = id

-- This should take one of the above functions and adapt it to operate on
-- FooBar values of compatible "type".  Values that match the given FooBar
-- constructor should be "unwrapped" and passed to the given function.
typeCheck :: (a -> FooBar) -> (a -> String) -> (FooBar -> Maybe String)
typeCheck constructor func fooBar = case fooBar of
  (constructor x) -> Just (func x)  -- GHC says "Parse error in pattern: constructor"
  _ -> Nothing

-- Define processing functions that operate on FooBars.
processFoo :: FooBar -> Maybe String
processFoo = typeCheck Foo processInt
processBar :: FooBar -> Maybe String
processBar = typeCheck Bar processString
2

2 Answers

3
votes

Interesting idea. I wonder what you're trying to do, since this is quite an unusual pattern matching problem.

You can certainly do it if you can:

  • enumerate the constructors of the type
  • have equality on the type's elements

Like so (I break out the application of f part, since that is orthogonal):

wasBuilt :: Eq t => (t -> Either t t)   -- ^ the constructor
                 -> Either t t          -- ^ a value
                 -> Maybe t             -- ^ the transformed result

wasBuilt k v = case v of
    Left  x | v == k x    -> Just x
    Right x | v == k x    -> Just x
    _                     -> Nothing

But there's a lot of boilerplate. This problem screams "generics" at me. Try a different approach, and reflect the constructor to data, then match generically on that data, perhaps. This will allow you to treat the constructors as values, instead of functions.


Here's roughly what I was thinking, but note, this is an advanced technique. Explicit pattern matching on a regular AST is much, much more idiomatic:

import Generics.SYB

-- only works for unary constructors
sameConstructor :: (Data a, Data b) => (a -> b) -> b -> Bool
sameConstructor k v = toConstr v == toConstr (k undefined)

> sameConstructor (Left :: Char -> Either Char Char) (Right 'x')
False

> sameConstructor (Left :: Char -> Either Char Char) (Left 'x')
True

> sameConstructor (:[]) "haskell"
True
2
votes

You might want to look at Type-safe pattern combinators functional pearl. While it does not mix that nicely with Haskell's pattern matching syntax, it does allow you to have the modularity of composable first-class patterns, if that's what you need (i.e. if the added composability outweighs the syntactic inconvenience).