purescript-deprecated / purescript-generics

21 stars 20 forks source link

Instance code generation #10

Open user471 opened 9 years ago

user471 commented 9 years ago

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
garyb commented 9 years ago

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.

user471 commented 9 years ago

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?

garyb commented 9 years ago

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.

user471 commented 9 years ago

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.

garyb commented 9 years ago

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 Foo

however, as explicitly named instances are here to stay.

user471 commented 9 years ago

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
garyb commented 9 years ago

That (Generic a) => Show a instance would have to be defined along with the Show class as otherwise it would be an orphan.

user471 commented 9 years ago

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

garyb commented 9 years ago

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).

user471 commented 9 years ago

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"
gbaz commented 9 years ago

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.

zudov commented 9 years ago

@garyb

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

Becomes kind of ugly and complicated though.