Defining Partially Used Types

Exploring the idea that typeclasses are essentially C ++ abstract classes without nested inheritance , I wrote typeclass

class Interface ic where i :: c -> i instance Interface ii where i = id infixl 1 # (#) :: Interface ic => c -> (i -> r) -> r c # f = f $ ic 

With type interface

 data IDrawable' = IDrawable { draw :: IO () } 

I would like to have something like

 type IDrawable c = Interface IDrawable' c 

So i can do

 data Object = Object { objectDraw :: IO () } data Person = Person { personDraw :: IO () } instance IDrawable Object where i = IDrawable . objectDraw instance IDrawable Person where i = IDrawable . personDraw 

While type IDrawable c compiles with ConstraintKinds , I am not allowed to do an instance IDrawable Object where i = IDrawable . objectDraw instance IDrawable Object where i = IDrawable . objectDraw with an error

 'i' is not a (visible) method of class 'IDrawable` 

Is there a way to declare IDrawable c = Interface IDrawable' c so that it can be set?

This is a purely academic interest, I do not recommend that anyone use this template in a real application, I just want to know if this is possible without using TemplateHaskell or CPP .

+8
haskell typeclass
source share
2 answers

No, this is not possible (since 7.8.3, and I think also 7.10); GHS Error # 7543 . This is not a very dangerous mistake; Obviously, there are at least a few people who would like to write such things (for example, you, Edward Kmett), but mostly this goes unnoticed. There is no progress in changing this behavior recorded on the tracker.

As for why you can't, let me rephrase Simon Peyton-Jones's explanation in the error tracker. The problem is that type checking instances have two parts: first, the GHC must look for where the method names are (here, i ); secondly, the GHC should expand type synonyms. Because these two steps are performed on this issue by two different components of the GHC, instances of the constraint syntax cannot be supported; The GHC cannot determine in which class it should look in order to find i .


Another reason this is a mistake - and the reason I found it, according to the comments of András Kovács answer , is that the current behavior is not as simple as "it does not work." Instead, it tries to work, but you cannot declare any methods ... but you can declare an instance without a method! In GHCi:

 GHCi, version 7.8.3: http://www.haskell.org/ghc/ :? for help ... Prelude> :set -XMultiParamTypeClasses -XFlexibleInstances -XConstraintKinds Prelude> class Interface ic where i :: c -> i Prelude> instance Interface ii where i = id Prelude> let (#) :: Interface ic => c -> (i -> r) -> r ; c # f = f $ ic ; infixl 1 # Prelude> data IDrawable' = IDrawable { draw :: IO () } Prelude> type IDrawable = Interface IDrawable' Prelude> instance IDrawable () where i _ = IDrawable $ return () <interactive>:8:29: 'i' is not a (visible) method of class 'IDrawable' Prelude> ()#draw <interactive>:9:3: No instance for (Interface IDrawable' ()) arising from a use of '#' In the expression: () # draw In an equation for 'it': it = () # draw Prelude> instance IDrawable () where {} <interactive>:10:10: Warning: No explicit implementation for 'i' In the instance declaration for 'Interface IDrawable' ()' Prelude> ()#draw *** Exception: <interactive>:10:10-21: No instance nor default method for class operation Ghci1.i 

In other words:

 instance IDrawable () where i _ = IDrawable $ return () 

fails but

 instance IDrawable () where {} 

succeeds! Thus, the check should be loosened or tightened depending on the desired behavior :-)


PS: One more thing: you should always use synonyms like-reduce as much as possible. This is why I changed IDrawable to

 type IDrawable = Interface IDrawable' 

and reset parameter c on both sides in the GHCi code above. The advantage of this is that since type synonyms cannot be partially applied, you cannot pass your version of IDrawable as a parameter to everything; however, a fully eta-reduced version can be passed anywhere, expecting something like * -> Constraint .

(This touched on András Kovács answer , and I mentioned it in the comment there, however, since I wrote the answer too, I decided that I 'and add it here.)

+1
source share

You can declare a "partial" class without instances:

 class Interface IDrawable' c => IDrawable c instance Interface IDrawable' Object where i = IDrawable . objectDraw instance Interface IDrawable' Person where i = IDrawable . personDraw 

Alternatively, you can use constraint syntaxes:

 type IDrawable c = Interface IDrawable' c 

It seems that a classy solution is preferable, since the IDrawable class has its own type * -> Constraint , while the type synonym is not applicable if it is not fully used. This can make a difference, because data definitions (both family types and almost all hackers at the type level) can only be used by constructors of the correct type.

+1
source share

All Articles