5

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 Answers 2

3

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
Sign up to request clarification or add additional context in comments.

7 Comments

It's an RPN calculator (practice program) that can have a mixture of different types (e.g. numbers and strings) on the stack. Most functions that operate on the stack will start by popping some values, checking that they're of the right type for the operation, and reporting an error if not. I'm trying to factor out that type-checking to avoid code duplication.
Ah! It will be a lot easier if you encode the type tags as simple values (e.g. Int values), instead of as constructor functions. That's what makes your example so tricky: pattern matching on functions is just hard.
Thanks, I'll consider using numeric type tags instead. Since my set of types is relatively limited, I could also just make a family of functions like typeCheckInt, typeCheckString, etc. with the appropriate constructor hard-coded in each. Since this is a practice program, I thought I'd try something a little more challenging, but it sounds like I may have bitten off more than I can chew. I'm familiar with generics in the context of C++ and Java, but not in the context of Haskell.
No, writing an RPN calculator is easy. You've just wandered into a funny design corner, by trying to pattern match on functions. Use simple types for type tags at runtime. If it is good enough for Simon Peyton Jones, it'll probably be ok for you too :-) Peyton Jones [1987, Chapter 10]
By "more than I can chew" I just meant this pattern-matching problem, not the whole calculator — I'm modeling it after the HP48's RPL environment, which I think is suitably challenging but still achievable. :-) I'm not sure how I'd avoid using an algebraic type for the values, though; I'm using a List as the stack, so it can't be heterogeneous. Or are you suggesting using using numeric tags in addition to the algebraic type?
|
2

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).

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.