4 ms·
"A type class defines a set of functions which must be implemented for a type to be considered in that type class. Other functions can then be written which ope
by badman_ting 12y ago
"A type class defines a set of functions which must be implemented for a type to be considered in that type class. Other functions can then be written which operate not on one specific type, but on any type which is in its given class constraint."
Call me crazy but this just sounds like a Java interface to me.
Edit: On further thought, I guess the difference is that in Java, the interface itself is a type. So all instances of classes which implement IShowable can be said to be of type IShowable. Whereas it seems that in Haskell a typeclass is not, itself, a type.
- tel 12y agoThere are a few other differences as well. I'm not completely familiar with the ins and outs of Java interfaces, but: * You can instantiate types to classes at any point in time (type definition, class definition, or even orphan instances, though that last category is frowned upon) * Typeclasses indicate typing bounds but do not destroy type information. This means that we can define things like showableId :: Show a => a -> a showableId x = x which allow only showable types to pass but does not destroy type information > showableId (3 :: Int) 3 :: Int > showableId (id :: Int -> Int) !! Type error * Typeclasses can dispatch on *any* type in the signature. This includes the famous "return type polymorphism" but generally means that typeclass resolution involves solving a terminating form of Prolog during typechecking. This means that type information flows forward and backward over judgements and allows for greater inference. * Typeclasses can abstract over higher kinded types. So we can write something like count :: Traversable t => t a -> Int count = getSum . getConst . traverse (const $ Const (Sum 1)) which generically counts the elements in any container instantiating the "interface" Traversable. There's also some even funkier techniques you can use when you start involving MultiParamTypeClasses, FunctionalDependencies, or TypeFamilies.
- sparkie 12y agoAn interface in OOP langs is more similar to the Existential data type which the author is trying to avoid using in this post, because it is sometimes seen as an anti-pattern in Haskell (although some people take this to the extreme and tell you to avoid Existentials completely). If we take for example, a simple IRenderable interface interface IRenderable { void Render(); } There's a bit of boilerplate to add, but we can get something pretty similar in Haskell: class Renderable a where render :: a -> IO () data Render = forall a. Renderable a => Render a instance Renderable Render where render (Render a) = render a The obvious difference between the author's proposed solution and this OOP-style interface is the open versus closed world assumption. By having an interface, we have an open world in which we can easily add new types to render, without changing existing code - only adding new instances. In the solution proposed by the author (and the various other "solutions"), they break the open world assumption and fall back to a closed world - where you need to create specific types which encapsulate all the known types that can be rendered - this is demonstrated by the author's Game and ExtendedGame types - if you need to create a new "ExtendedExtendedGame" type each time you add a new renderable type - this solution obviously does not scale, does it? With the Existential now, we can create a list of Render, which would be similar to having a list of IRenderable in Java. We can't do anything with the list other than call render on each item - which is almost always what we want to do anyway - so the oft-reported "existential anti-pattern" is usually no such thing. In fact, the claim that this is an antipattern stems from the idea that existentials are "type-erasing" - that you might want to convert back from a Render to a Ball for instance (or from an IRenderable to a Ball). Even in Java, this would be a bad idea - it requires an explicit cast which could fail at runtime - you simply wouldn't do such thing unless you were certain of its type, or you guarded such cast by first using the `instanceof` operator. if (r instanceof Ball) { Ball b = (Ball)r; ... } You would not normally do this directly on each type, as you'd be back to a closed-world assumption. Instead, you'd usually create a mapping of types->functions, where you can dynamically test a type and do the relevant action, and you can continue to add new items to the map. In the event we do want a list of renderable items, and we don't want to lose type information - Haskell can also provide a similar type-cast to the one you'd use in Java - it's rightly called `unsafeCoerce` because it is unsafe - as is the Java version, which throws a ClassCastException when used incorrectly. If we modify the existential data type to also include type information, via Haskell's Data.Typeable module, we can encapsulate the bad behavior of unsafeCoerce, and ensure that we only expose a safe version - one that returns "Maybe x" instead of "x, but may fail". {-# LANGUAGE ExistentialQuantification #-} module X.Render ( Render, toRender, fromRender ) where import Data.Typeable import Unsafe.Coerce data Render = forall a. (Typeable a, Renderable a) => Render TypeRep a instance Renderable Render where render (Render _ a) = render a toRender :: (Typeable a, Renderable a) => a -> Render toRender a = Render (typeOf a) a fromRender :: (Typeable a, Renderable a) => Render -> Maybe a fromRender (Render t a) = case unsafeCoerce a of x | t == typeOf x -> Just x | otherwise -> Nothing You can even go as far as emulating Java's instanceof operator (althought not quite the same - it doesn't handle subtyping), just create a function instanceof in a typeclass, and use it as an infix operator. class InstanceOf a where instanceOf :: a -> TypeRep -> Bool instance InstanceOf Render where instanceOf (Render tReal _) tWanted | tReal == tWanted = True instanceOf _ _ = False ... (toRender Ball) `instanceOf` (typeOf Ball) == True