maximilian
maximilian

Reputation: 23

Match Data constructor functions

I'm trying to match data constructors in a generic way, so that any Task of a certain type will be executed.

data Task = TaskTypeA Int | TaskTypeB (Float,Float)

genericTasks :: StateLikeMonad s
genericTasks = do
   want (TaskTypeA 5)

   TaskTypeA #> \input -> do 
       want (TaskTypeB (1.2,4.3))
       runTaskTypeA input

   TaskTypeB #> \(x,y) -> runTaskTypeB x y

main = runTask genericTasks

In this, the genericTasks function goes through the do-instructions, building a list of stuff to do from want handled by some sort of state monad, and a list of ways to do it, via the (#>) function. The runTask function will run the genericTasks, use the resulting list of to-do and how-to-do, and do the computations.

However, I'm having quite some trouble figuring out how to extract the "type" (TaskTypeA,B) from (#>), such that one can call it later. If you do a :t TaskTypeA, you get a Int -> Task.

I.e., How to write (#>)?

I'm also not entirely confident that it's possible to do what I'm thinking here in such a generic way. For reference, I'm trying to build something similar to the Shake library, where (#>) is similar to (*>). However Shake uses a String as the argument to (*>), so the matching is done entirely using String matching. I'd like to do it without requiring strings.

Upvotes: 2

Views: 1163

Answers (1)

luqui
luqui

Reputation: 60463

Your intuition is correct, it's not possible to write (#>) as you have specified. The only time a data constructor acts as a pattern is when it is in pattern position, namely, appearing as a parameter to a function

f (TaskTypeA z) = ...

as one of the alternatives of a case statement

case tt of
    TaskTypeA z -> ...

or in a monadic or pattern binding

do TaskTypeA z <- Just tt
   return z

When used in value position (e.g. as an argument to a function), it loses its patterny nature and becomes a regular function. That means, unfortunately, that you cannot abstract over patterns this easily.

There is, however, a simple formalization of patterns:

type Pattern d a = d -> Maybe a

It's a little bit of work to make them.

taskTypeA :: Pattern Task Int
taskTypeA (TaskTypeA z) = Just z
taskTypeA _ = Nothing

If you also need need to use the constructor "forwards" (i.e. a -> d), then you could pair the two together (plus some functions to work with it):

data Constructor d a = Constructor (a -> d) (d -> Maybe a)

apply :: Constructor d a -> a -> d
apply (Constructor f _) = f

match :: Constructor d a -> d -> Maybe a
match (Constructor _ m) = m

taskTypeA :: Constructor Task Int
taskTypeA = Constructor TaskTypeA $ \case TaskTypeA z -> Just z
                                          _ -> Nothing

This is known as a "prism", and (a very general form of) it is implemented in lens.

There are advantages to using an abstraction like this -- namely, that you can construct prisms which may have more structure than data types are allowed to (e.g. d can be a function type), and you can write functions that operate on constructors, composing simpler ones to make more complex ones generically.

If you are using plain data types, though, it is a pain to have to implement the Constructor objects for each constructor like I did for TaskTypeA above. If you have a lot of these to work with, you can use Template Haskell to write your boilerplate for you. The necessary Template Haskell routine is already implemented in lens -- it may be worth it to learn how to use the lens library because of that. (But it can be a bit daunting to navigate)

(Style note: the second Constructor above and its two helper functions can be written equivalently using a little trick:

data Constructor d a = Constructor { apply :: a -> d, match :: d -> Maybe a }

)

With this abstraction in place, it is now possible to write (#>). A simple example would be

(#>) :: Constructor d a -> (a -> State d ()) -> State d ()
cons #> f = do
    d <- get
    case match cons d of
        Nothing -> return ()
        Just a  -> f a

or perhaps something more sophisticated, depending on what precisely you want.

Upvotes: 2

Related Questions