0
votes

I got a custom data type schematically defined like this:

data Foo = Foo
  { f1 :: Int
  , f2 :: b
  , f3 :: c
  }

What i need to do

Attach a String to every function recorded in Foo. That was pretty clear in my head that it was trivial due to haskell polymorphism.


First try: classes

class Value a where
  val :: a -> String
class Query e where
  queries :: (Value a) => [(e -> a, String)]

instance Value Int where val = show
  instance Query Foo where
    queries =
      [ (f1, "function one")
      , (other records of Foo…)
      ]

Which doesn't work. I'm not sure i figured out why. But i think ghc expects a function of type (Foo -> a) but gets a function of type (Foo -> Int). So, the polymorphism doesn't apply here.


Second try: pattern matching

keyOf :: (Foo -> a) -> String
keyOf f1 = "function one"
keyOf f2 = "function two"
keyOf f3 = "function three"
keyOf _  = "unknown function"

I was quite satisfied seeing it would compile. Then, in ghci:

λ keyOf f2 = "function one"

Can't pattern match against a function name, obviously…


EDIT: Why i need to do it

Construct a query string like this:

"(keyOf f1)=(f1 Foo), (keyOf f2)=(f2 Foo), (keyOf f3)=(f3 Foo)"

More generally, fold every function recorded in Foo with the String associated with it, and it's result. Example:

exampleFoo :: Foo
exampleFoo = Foo "one" "two" "three"

assocs = [(f1, "function one"), (f2, "function two"), (f3, "function three")]

result == "function one=one, function two=two, function three=three"

Now, i really wonder if that kind of trick is doable without involving meta-programming (like TemplateHaskell) in haskell. Any options i didn't consider ? Thanks.

2
Can you give an example of a program you'd like to write using the Strings attached to each function?GS - Apologise to Monica
yes, i edited the question. Is this clear enough ?qleguennec
Afraid not - I can't make out what you mean about the query string. Might be clearer with some sample code or pseudo-code.GS - Apologise to Monica
edited. I'm aware this is a bit confused: i know i'm thinking wrong, but i can't think of another design.qleguennec
I think I understand now - see my answer.GS - Apologise to Monica

2 Answers

2
votes

I think what you're looking for here is an existential.

If you define

data Selector e where
    Selector :: Value a => (e -> a) -> Selector e

(you'll need {-# LANGUAGE GADTs #-} at the top of the file)

Then you can define your class Query as:

class Query e where
    queries :: [(Selector e, String)]

instance Query Foo where
    queries =
        [ (Selector f1, "function one")
        , (other records of Foo…)
        ]

The problem with your previous definition was that an implementation of queries had to contain functions that could produce any a type. What you want is that each element of queries produces a specific type of your choice. That's what the Selector type does - it hides that specific type so that you get to choose it rather than the caller of queries getting to choose.

In this particular example, the only thing you can do with the a type in Selector is turn it into a String with Value, so you could just as well have written:

class Query e where
    queries :: [(e -> String, String)]

instance Query Foo where
    queries =
        [ (value . f1, "function one")
        , (other records of Foo…)
        ]

However if Value were a more complicated typeclass, particularly one that's generally useful, then this flattening would get quite verbose.

0
votes

I think the main problem is that you're confusing how data works.

By looking at

keyOf :: (Foo -> a) -> String
keyOf f1 = "function one"
keyOf f2 = "function two"
keyOf f3 = "function three"
keyOf _  = "unknown function"

I think that maybe your data should look like this:

data Foo b c = F1 Int | F2 b | F3 c

That way, the pattern match would be:

keyOf :: Foo b c -> String
keyOf (F1 _) = "function one"
keyOf (F2 _) = "function two"
keyOf (F3 _) = "function three"

The "unknown function" is not needded, as we are pattern matching every possible Foo constructor.