Instance code generation
Opened this issue · 12 comments
Is there any plan to generate this code somehow automatically?
instance showFoo :: Show Foo where
show = gShow
instance eqFoo :: Eq Foo where
eq = gEq
instance ordFoo :: Ord Foo where
compare = gCompare
I don't think so. The idea of only adding generic deriving to the compiler is so we don't have to add specific cases for all the potentially derivable classes that may come along - as the prelude isn't included in the compiler there's no guarantee that Show, Eq, Ord, etc. will even exist in someone's project.
We're hoping to do something similar to generic deriving with newtype deriving though, which means you won't have to write explicit instances for newtypes at least.
But what is wrong with Haskell deriving syntax sugar?
data Foo = Foo Number String deriving( Eq, Show, Ord )
could generate all this code.
Why cannot PS have this feature?
The idea of only adding generic deriving to the compiler is so we don't have to add specific cases for all the potentially derivable classes that may come along - as the prelude isn't included in the compiler there's no guarantee that
Show,Eq,Ord, etc. will even exist in someone's project.
So? Why does compiler need to know about specific cases?
We could write something like this
instance genericShow :: forall a. (Generic a) => Show a where
show = gShow
which means generic Show implementation
And when compiler see this
data Foo = Foo Number String deriving(Show)
It search for generic Show implementation and use it to generate the instance code
instance showFoo :: Show Foo where
show = gShow
I don't see anything specific.
The first instance would be a potential option but having an instance like that precludes the ability of writing custom Show classes for any type that you create a Generic instance for, due to instance overlaps - so you'd either have the choice of fully Generic or fully custom, the way things are now you have the option of mixing hand written and generic-based instances.
One option might be to somehow enhance class definitions to have a way of describing what an instance based on generics would look like, as that way yes, the compiler wouldn't need to know specifics. The deriving syntax would probably still be something like:
derive instance showFoo :: Show Foohowever, as explicitly named instances are here to stay.
instance like that precludes the ability of writing custom Show classes for any type that you create a Generic instance for
I don't get it. No one force you to use deriving(Show) syntax. You can always do everything manually.
If we really want flexibility even for deriving then we could use import restriction.
module M1
instance genericShow :: forall a. (Generic a) => Show a where
show = gShow
module M2
instance otherGenericShow :: forall a. (Generic a) => Show a where
show = gShow2
import M1
data Foo = Foo Number String deriving(Show) // genericShow
import M2
data Foo = Foo Number String deriving(Show) // otherGenericShow
That (Generic a) => Show a instance would have to be defined along with the Show class as otherwise it would be an orphan.
Yes, but you should be able to define more specific instance
instance genericShow :: forall a. (Generic a) => Show a where
show = gShow
instance showFoo :: Show Foo where
show = "bar"
test = show $ Foo 1 "foo"
test == "bar" because showFoo more specific than genericShow
Unfortunately that isn't how instance resolution works, the concept of "more specific" gets rather slippery when you have multi-parameter typeclasses or instances with class contexts. It may still be worth exploring, but I suspect that someone more knowledgable than me will have a reason for why it's not allowed (Haskell also doesn't work this way).
Maybe it cannot work with all scenarios, but it differently could walk with simple one like this.
There are many other possible solutions.
What about import restriction?
If there are two suitable instances
module Data.Generic
instance genericShow :: forall a. (Generic a) => Show a where
show = gShow
module M
instance showFoo :: Show Foo where
show = "bar"
import Data.Generic
import M
test = show $ Foo 1 "foo"
Compiler will force you to hide one instance
import Data.Generic hiding (genericShow)
import M
test = show $ Foo 1 "foo"
A syntax proposal from the channel:
derivable class Generic a => Show a where
show = gShow
instance showFoo :: Show Foo
i.e. a "derivable class" is sort of like a "default instance"
If you declare one -- and I guess really only one can exist per class, then if you declare an instance without methods, the derivable instance, if it matches, will fill in the instances for you...
Not arguing for this now or ever necessarily, but recording it for posterity as a sane approach.
there's no guarantee that Show, Eq, Ord, etc. will even exist in someone's project.
I think there is. For example in haskell -XDerivingGeneric wouldn't allow you to say deriving Generic unless you import Data.Generic.
The problem is of course (as you said earlier) that if someone defines their own Show compiler might not be able to figure out that it's not the Show that we were looking for.
We could go a bit duck-typish here, and do something like
- If the user asks to
derive (Show), check thatShowis in the scopeShowin the scope, is exactly the same as theShowwhich we are thinking about, namely- has a single
*in the head - has one method
show :: a -> String
- has a single
- and if all that holds true, derive a Show instance
Becomes kind of ugly and complicated though.