Determining the constructor of a data type instance

114 Views Asked by At

have I coded myself into a corner? I have a data type that's near the entry point of my program

data Token = A String | B String deriving (Eq, Show, Read)

In a library module, I have

type Constructor token = String -> token

Back towards the entry point, I have something like

constructor :: Constructor Token
constructor = A
tokenA :: Token
tokenA = A "a"
tokenB :: Token
tokenB = B "b"

Somewhere in the middle, between entry point and library, what I want to use is

tokenUsesConstructor :: Constructor token -> token -> Bool

so that some things can be done to the As and different things can be done to the Bs. The following doesn't work ofc because parameters cannot be repeated:

tokenUsesConstructor constructor (constructor _) = True
tokenUsesConstructor constructor (_ _)           = False
-- use.hs:21:35: error: Parse error in pattern: constructor

and, as the error shows (indicating the first of the two lines above), the problem is more fundamental than that, as is also shown by

tokenUsesConstructor constructor token =
    case token of
        constructor _ -> True
         _            -> False

which gets the same error (the former function is just a sugared version of the latter anyway, I think). If I could get the string, I could construct and compare but that's effectively the same problem with respect to the matching.

Can an instance of a data type be deconstructed without pattern-matching? I'd rather not add more to the data definition or turn it into a record type.

(BTW, I have got around this by creating an arbitrary token from the constructor, showing the original and constructed terms and then comparing the heads of the words but it feels like a work-around.)

1

There are 1 best solutions below

2
On

The constructor part of a pattern must be static; it can’t be a variable. You also can’t use the same variable multiple times in a pattern (“nonlinear” patterns), or use a global variable name as a pattern to try to match that thing by equality; a variable name in a pattern always matches anything, and binds it to a new local variable by that name.

Furthermore, functions are completely opaque and cannot be compared. Even if they could, that wouldn’t be the right thing here: Constructor token denotes any function of type String -> token, not necessarily one of the constructors of your Token type.

If you want to do this check dynamically, you can make a separate tag type for tokens and compare against that:

data Token = A String | B String
  deriving (Eq, Read, Show)

data Constructor = ConstructorA | ConstructorB
  deriving (Eq, Read, Show)

tokenConstructor :: Token -> Constructor
tokenConstructor A{} = ConstructorA
tokenConstructor B{} = ConstructorB

tokenUsesConstructor :: Constructor -> Token -> Bool
tokenUsesConstructor c t = tokenConstructor t == c

If, as it seems, all of your constructors just take a String argument, then you can factor out the repetition with a little type algebra (t + t = 2 × t):

data Token = Token
  { tokenConstructor :: Constructor
  , tokenString :: String
  }
  deriving (Eq, Read, Show)

tokenUsesConstructor c t = tokenConstructor t == c

Alternatively, you could have this function accept a predicate as an argument, then the test is trivial (and you might not even need a separate function):

isA :: Token -> Bool
isA A{} = True
isA _ = False

isB :: Token -> Bool
isB B{} = True
isB _ = False

type Predicate a = a -> Bool

matches :: Predicate token -> token -> Bool
matches p t = p t  -- matches = ($) = id
> constructor = isA
> matches constructor tokenA
True

There are more involved solutions, such as GADTs to make this “tag” known statically, or various ways of making things generic, but I think whether they’re appropriate depends on the details of your actual use case that you haven’t described.