1

I have the following:

data Dog =
  Dog
  { _x :: Int
  }
makeFieldsNoPrefix ''Dog

data Cat =
  Cat
  { _dog :: Dog
  }
makeFieldsNoPrefix ''Cat

This gives me a HasX and a HasDog class along with instances instance HasDog Cat Dog and instance HasX Dog Int

But i'd also like to generate instance HasX Cat Int, is this TH functionality available somewhere?

Charles Durham
  • 1,707
  • 11
  • 17
  • Is there a reason why you want to do this with template Haskell instead of defining it yourself. You here attach a rather specific meaning to it, and as a result I do not think this is automated. – Willem Van Onsem May 23 '18 at 20:12
  • @WillemVanOnsem Yeah, I have a bunch of different config types with lots of values that hold others with lots of values, so for a given carrier config, i'd have to hand code every instance for every field of every datatype it holds. – Charles Durham May 23 '18 at 20:55
  • Why not just compose the lens' that are auto generated? – jkeuhlen May 23 '18 at 20:58
  • @jkeuhlen the whole reason I want this is so I can have the type system infer all the "HasX" fields it relies on in the signature. Which is much preferred to every function being `f :: Config -> ...`, when I can have `f :: HasX c, HasY c => c -> ...` So it's still obvious what values each function relies on. If I compose the lenses inside the function, it loses this generality of structure. – Charles Durham May 23 '18 at 21:08
  • 2
    @CharlesDurham Then what's supposed to happen in a case like `data Dog = Dog { _x :: Int }`, `data Hamster = Hamster { _x :: Int }`, `data Petshop = Petshop { _dog :: Dog, _hamster :: Hamster} `? Is Petshop supposed to have a `HasX` instance? If so, what would the implementation be? – Cubic May 23 '18 at 23:14
  • @Cubic Good example, but I'd imagine there could be a separate name field in a function like deriveBy ''Petshop ''Hamster – Charles Durham May 23 '18 at 23:46

1 Answers1

1

There are at least two good reasons makeFields doesn't generate Has instances for transitively included fields.

  1. In a big structure consisting of many datatypes, it would be slow (O(n^2)) to generate Has instances for every field which is included somewhere in the structure transitively.
  2. There is not always an obvious choice of which x to choose. Consider

    data TwoDogs = { _dog1 :: Dog, _dog2 :: Dog }
    

    Which Dog's _x should TwoDogs's HasX lens access? It's a Lens, not a Traversal, so you have to pick one. It's not reasonable to expect an automated tool to make that decision for you.

Fortunately lenses are very easy to compose. If you want your type to have a HasX instance which identifies a particular _x somewhere nested in the structure (according to some domain-specific rule) you can just write one.

instance HasX Cat Int where x = dog.x
Benjamin Hodgson
  • 42,952
  • 15
  • 108
  • 157
  • Yeah, that was Cubic's argument in the comment below my question, though I am surprised there isn't a TH function that says "derive all of dog1's instances as if they belong to TwoDogs" – Charles Durham May 24 '18 at 16:38
  • (accidentally hit enter instead of shift-enter) makeFields is just so damn convenient, especially for the case when you have 43 constants in a config lol, might just put the whole tree structure linearly in one datatype. – Charles Durham May 24 '18 at 16:44
  • @CharlesDurham It's not really that surprising, considering it has obvious semantics and performance problems and is really not at all useful for most people. – Cubic May 24 '18 at 17:16
  • @CharlesDurham If you feel like it's a missing feature you can write your own TH helper :) – Benjamin Hodgson May 24 '18 at 17:19
  • @Cubic I disagree that it would have performance issues, especially if you get exactly the amount of instances that you wish to have and would write anyway. Also, I would bet that something like this would be very useful to most people, especially with how many applications pass an environment around and lose the type level info of what each function is actually pulling from the environment (think ReaderMonad). You could also block accidental access to fields that shouldn't be read for the implementation of one function. env -> x functions are not descriptive at all. – Charles Durham May 24 '18 at 18:29