Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What can you do that is useful with Haskell Type Classes?

Tags:

haskell

I understand that type classes are very useful for organizing data and for type checking etc., but other than what is already included in prelude, is there ever a need to define your own class?

Under almost any circumstance one could just define a data or newtype and get almost the same effect anyways. Using the built in "Ord", "Eq", "Show", and others seems to be enough to do anything you would want to do with classes.

When I looked up projects for classes in Haskell I get a lot of example classes like so:

class Foo a where
    bar :: a -> a -> Bool

If there is a reason to use type classes, does anyone have a good project for a beginner to practice using them or some good form guides for them?

like image 587
BryceTheGrand Avatar asked Aug 01 '19 13:08

BryceTheGrand


People also ask

What are Typeclasses used for?

A typeclass is a sort of interface that defines some behavior. If a type is a part of a typeclass, that means that it supports and implements the behavior the typeclass describes. A lot of people coming from OOP get confused by typeclasses because they think they are like classes in object oriented languages.

What is an instance of a Haskell type class?

An instance of a class is an individual object which belongs to that class. In Haskell, the class system is (roughly speaking) a way to group similar types. (This is the reason we call them "type classes"). An instance of a class is an individual type which belongs to that class.

How are types used in Haskell?

In Haskell, every statement is considered as a mathematical expression and the category of this expression is called as a Type. You can say that "Type" is the data type of the expression used at compile time. To learn more about the Type, we will use the ":t" command.

What are type classes in Haskell?

Type Classes are a language mechanism in Haskell designed to support general overloading in a principled way. They address each of the concerns raised above. They provide concise types to describe overloaded functions, so there is no expo- nential blow-up in the number of versions of an overloaded function.


3 Answers

Type classes provide ad hoc polymorphism, as opposed to parametric polymorphism: a function does not need to be defined the same way (or at all) for each type. In addition, it does so in an open fashion: you don't need to enumerate all the types that implement the class when you define the class itself.

Some prominent examples of non-standard type classes are the various MonadFoo classes provided by mtl (monad transformer library), ToJSON and FromJSON provided by the aeson library, and IsString, which makes the OverloadedString extension work.


Without type classes, you can define a function that works for a single argument type

foo :: Int -> Int

or one that works for all argument types

foo :: a -> Int

The only way to work for some subset of types is to use a sum type

foo :: Either Int Bool -> Int

but you can't later define foo for Float without changing the type of foo itself

foo :: Either Int (Either Bool Float) -> Int

or

data IntBoolFloat = T1 Int | T2 Bool | T3 Float
foo :: IntBoolFloat -> Int

either or which will be cumbersome to work with.

Typeclasses let you work with one type at a time, and let you add new types in a nonintrusive fashion.

class ToInt a where
    foo :: a -> Int

instance ToInt Int where foo = id
instance ToInt Bool where
    foo True = 1
    foo False = 2
instance ToInt Float where
    foo x = 3  -- Kind of pointless, but valid

An instance of ToInt can be defined anywhere, although in practice it's a good idea for it to be defined either in the module where the class itself is defined, or in the module where the type being instantiated is defined.


Underneath the hood, a method (a function defined by a type class) is essentially a mapping of types to functions. The TypeApplications extension makes that more explicit. For example, the following are equivalent.

foo True == 1
foo @Bool True == 1 -- foo :: ToInt a => a -> Int, but foo @Bool :: Bool -> Int
like image 195
chepner Avatar answered Nov 20 '22 04:11

chepner


The definition

class Foo a where
    bar :: a -> a -> Bool

is very simililar to

class Eq a where
    (==) :: a -> a -> Bool 
    (/=) :: a -> a -> Bool 

and here is when you can find how useful can it be:

Imagine you've got slugs, and want to know if they can procreate, and there is a rare species with hermaphrodite type, you can use your typeclass:

data Slug = M | F | H

class Foo a where
    bar :: a -> a -> Bool

instance Foo Slug where
   bar H _ = True
   bar _ H = True
   bar F M = True
   bar M F = True
   bar _ _ = False  

Or, with temperatures: You want to know if mixing water will get you warm water:

data Temp = Cold | Hot | Warm

instance Foo Temp where
   bar Warm _    = True
   bar _ Warm    = True
   bar Hot Cold  = True
   bar Cold Hot  = True
   bar _ _       = False  

So, that typeclass now could be named sort of "Mixable", and the method, "mix", and it would be less confusing to read for type Slug and Temperature.

Now, if you want to watch it in action with some example, I can came up now with something like...

mix :: Foo a => [a] -> [a] -> [Bool]
mix xs ys = zipWith bar xs ys

$>  mix [M,M,H,F] [F,F,F,F]
=> [True,True,True,False]

but there is a restriction with mix, you can just mix Mixable things. so if you do:

mix [1,1] [2,2]

will break:

9:1: error:
    • No instance for (Foo Bool) arising from a use of ‘mix’
    • In the expression: mix [True, True] [False, True]
      In an equation for ‘it’: it = mix [True, True] [False, 

And that means, that you can organize you data types to satisfy the mix function according its structure, or your needs.

Level 2:

What if you want a default implementation for Slug and Temp? Because you saw they where similar, so you could do:

class (Bounded a, Eq a) => Mixable a where
    mix :: a -> a -> Bool
    mix e1 e2 = e1 /= e2 || any (\x -> x /= minBound && x /= maxBound) [e1, e2]

data Slug = F | H | M deriving (Bounded, Eq, Show)
data Temp = Cold | Warm | Hot deriving (Bounded, Eq, Show)

instance Mixable Slug
instance Mixable Temp

mixAll :: Mixable a => [a] -> [a] -> [Bool]
mixAll xs ys = zipWith mix xs ys

main = do
  putStrLn $ show (mixAll [F,F,F,M,M,M,H] [F,M,H,M,F,H,H])
  putStrLn $ show (mixAll [Cold,Cold,Cold,Hot,Hot,Hot,Warm] [Cold,Hot,Warm,Hot,Cold,Warm,Warm])

[False,True,True,False,True,True,True]

[False,True,True,False,True,True,True]

like image 33
A Monad is a Monoid Avatar answered Nov 20 '22 05:11

A Monad is a Monoid


In adition to chepner's explanation of the usefulness of type classes, here are some more practical examples of type classes outside of Prelude:

  • Arbitrary from QuickCheck (A QuickCheck Tutorial: Generators).
  • Example from Hspec, which is similar but not exactly equivalent.
  • WithLog (or rather, HasLog) from co-log.
  • SafeCopy is another serialization class, but with different constraints than Aeson's FromJSON, ToJSON, since it also deals with data format migrations.

Since there's a whole design space for using type classes in different ways, here are some more thoughts:

  • The Has Type Class Pattern: Tutorial 1, Tutorial 2, and the package data-has.

  • An interesting library related to QuickCheck is Hedgehog, which does away with type classes for a strong reason (tutorial and generally an eye-opener). So there may be lots of reasons to use and not use type classes; often there simply already exists exactly the type class you're looking for.

  • It may be worth to read Gabriel Gonzalez' Scrap Your Type Classes which highlights some of the downsides of the uses of type classes. As the blog post starts with, his "opinion on type classes has mellowed since I wrote this post, but I still keep it around as a critique against the excesses of type classes."

If there is a reason to use type classes, does anyone have a good project for a beginner to practice using them or some good form guides for them?

It really depends on whether you want to define a type class, or just define type class instances for existing type classes, use an existing type class in base, or use some type class in an extended library.

It can be fun to define type class instances for things that are Monoid or Semigroup (tutorial). It can also be fun to define your own ToJSON and FromJSON instances for some JSON data format that you might find interesting (tutorial).

like image 36
sshine Avatar answered Nov 20 '22 04:11

sshine