The type class patterns are simple but useful. I’ll walk through some examples. Has surprisingly Use Case 1: Collecting all Images Imagine you are working on a game and during an asset validation step, you want to make sure that the scene’s images exist. We need to traverse the scene and collect all the image file paths. Let’s make a type: description Scene data Scene = Scene{ backgroundImage :: Text, characters :: [Character], bewilderedTourist :: Maybe Character, objects :: [Either Rock WoodenCrate]} data Character = Character{ hat :: Maybe DamageArray, head :: DamageArray, torso :: DamageArray, legs :: DamageArray, shoes :: Maybe DamageArray} data DamageArray = DamageArray{ noDamage :: Text, someDamage :: Text, excessiveDamage :: Text} data Rock = Rock{ weight :: Double, rockImage :: Text} data WoodenCrate = WoodenCrate{ strength :: Double, woodenCrateImage :: DamageArray} You get the idea. There are a lot of types, with a fair amount of nesting. A real scene could have hundreds of types and double digit levels of nesting, but this is a good enough example for our purposes. So now we want to write a function, . collectImages :: Scene -> Set Text You haz no Has The most straightforward approach is to just write the functions: collectImages :: Scene -> Set TextcollectImages Scene {..}= singleton backgroundImage<> mconcat (map collectCharacterImages characters)<> maybe mempty collectCharacterImages bewilderedTourist<> mconcat (map (either (singleton . collectRockImage)collectWoodenCrateImages)objects) collectCharacterImages :: Character -> Set TextcollectCharacterImages Character {..}= maybe mempty collectDamageArrayImages hat<> collectDamageArrayImages head<> collectDamageArrayImages torso<> collectDamageArrayImages legs<> maybe mempty collectDamageArrayImages shoes collectDamageArrayImages :: DamageArray -> Set TextcollectDamageArrayImages DamageArray {..} = fromList[ noDamage, someDamage, excessiveDamage] collectRockImage :: Rock -> TextcollectRockImage Rock {..} = rockImage collectWoodenCrateImages :: WoodenCrate -> Set TextcollectWoodenCrateImages WoodenCrate {..} =collectDamageArrayImages woodenCrateImage The code is verbose and a little tedious, but not terribly difficult to write or follow. I was disciplined and named everything in a consistent way, which made it easy remember. The trickiest part is just remembering what helper functions to call when operating on my polymorphic containers (all the s and stuff). maybe mconcat All the Has in the World Here is the same code written with a variation of the type class pattern: Has class HasImages a whereimages :: a -> Set Text instance HasImages a => HasImages [a] whereimages xs = foldr (\x accum -> images x <> accum) mempty xs instance HasImages a => HasImages (Maybe a) whereimages x = maybe [] images x instance (HasImages a, HasImages b) => HasImages (Either a b) whereimages x = either images images x instance HasImages Scene whereimages backgroundImage<> images characters images bewilderedTourist<> images objects Scene {..} = singleton <> instance HasImages Character whereimages Character {..}= images hat<> images head<> images torso<> images legs<> images shoes instance HasImages DamageArray whereimages DamageArray {..} = fromList[ noDamage, someDamage, excessiveDamage] instance HasImages Rock whereimages Rock {..} = singleton rockImage instance HasImages WoodenCrate whereimages WoodenCrate {..} = images woodenCrateImage Alright, so this the simplest variation of the type class pattern. We have a type class which requires a function, , to be implemented by each instance. Has HasImages a -> Set Text The first difference between the example and the prior example is that I have implemented generic functions for my polymorphic containers , , and . The value in this approach is that I don’t have to think about what functions to call to collect the images: it’s always . In the prior example, I had to think about the how to collect the images each time, and it took brain power better spent elsewhere. Has [] Maybe Either images The benefits of the pattern are: Has I can write a single instance for each polymorphic container that will work for all specializations I need. I do not have to make as many decisions, freeing up brain power to focus on other things It provides structure for other engineers to extend without having to make decisions about names of functions and what the type the functions should be (see the inconsistency from the first example). collectRockImage The downsides are: It uses a type class, which is a more complicated concept than a function. 2. The instance declaration is noisier than the function declaration and required greater indention. Use Case 2: Composable Reader The pattern can also be used to create a composable Reader monad. Has Say you have library A with the following: foo :: Reader Int Bool and library B with: bar :: Reader String Int and you would like to be able to write foobar = doflag <- foo if flag thenbarelsereturn 0 but it won’t type check, because needs an environment and needs a environment. foo Int bar String The trick is to define the helper type classes: Has class HasFooEnv a wheregetFooEnv :: a -> Int class HasBarEnv a wheregetBarEnv :: a -> String Then we modify the type signatures to use : MonadReader foo :: (MonadReader e m, HasFooEnv e) => m Bool We will have to modify calls to to use . We make a similar modification for : ask asks getFooEnv bar bar :: (MonadReader e m, HasBarEnv e) => m Int and instances: instance HasFooEnv (Int, String) wheregetFooEnv = fst instance HasBarEnv (Int, String) wheregetBarEnv = snd We get the combined version to type check: foobar :: Reader (Int, String)foobar = doflag <- foo if flag thenbarelsereturn 0 Michael Snoyman also discusses the pattern in a post on ReaderT . Has here Use Case 3: Convenient Argument Passing It is quite common in database applications to have the following types: newtype Key a = Key UUIDdata Entity a = Entity{ entityKey :: Key a, entityValue :: a} Additionally, one will write queries that look like: getFriends :: Key User -> [Entity User] which will get called often by extracting a from an . Key User Entity User getFriends (entityKey user) and you can make the API just ever so slightly easier to use with: class HasKey a k | a -> k wherekey :: a -> Key k instance HasKey (Key a) a wherekey = id instance HasKey (Entity a) a wherekey = entityKey getFriends :: HasKey a User => a -> [Entity User] and one can now pass in either a or . Entity User Key User getFriends user It’s a small thing, but one of my past coworkers liked it and I do myself so I’m including it. More difficult error messages is a downside. Use Case 4: Traversals So far we are discussing a simple version of that can only get things. This is only the beginning. Going back to our first example, let’s say that instead of merely collecting the images, we also want to traverse the scene and update the image file paths with the hash of the image as a suffix. Has We are going to take advantage of the package and our new class will look like: lens HasImages class HasImages a whereimages :: Traversal' a Text Our instances look like: instance HasImages a => HasImages [a] whereimages = traversed . images instance HasImages a => HasImages (Maybe a) whereimages = traversed . images instance (HasImages a, HasImages b) => HasImages (Either a b) whereimages f e = case e ofLeft x -> Left <$> traverseOf images f xRight x -> Right <$> traverseOf images f x instance HasImages Scene whereimages f Scene {..}= Scene<$> f backgroundImage<*> traverseOf images f characters<*> traverseOf images f bewilderedTourist<*> traverseOf images f objects instance HasImages Character whereimages f Character {..}= Character<$> traverseOf images f hat<*> traverseOf images f head<*> traverseOf images f torso<*> traverseOf images f legs<*> traverseOf images f shoes instance HasImages DamageArray whereimages f DamageArray {..}= DamageArray<$> f noDamage<*> f someDamage<*> f excessiveDamage instance HasImages Rock whereimages f Rock {..}= Rock weight<$> f rockImage instance HasImages WoodenCrate whereimages f WoodenCrate {..}= WoodenCrate strength<$> traverseOf images f woodenCrateImage We can apply our hash updater like: hashFilePath :: Text -> IO TexthashFilePath filePath = dolet pathStr = T.unpack filePathfileHash <- hashBytes <$> BSL.readFile pathStrreturn $ T.pack $ dropExtension pathStr++ "-" ++ fileHash <.> takeExtension pathStr hashSceneImages :: Scene -> IO ScenehashSceneImages x = traverseOf images hashFilePath x Not only that, but we get our for “free” (although the performance is going to be different, which probably doesn’t matter). collectImages collectImages :: Scene -> [Text]collectImages x = fromList $ toListOf images x Use Case 5: Composable State We can get a composable State monad like we got a composable Reader monad by using a instance of simple function: Lens class HasFooState a wherefooState :: Lens' a Int class HasBarState a wherebarState :: Lens' a String then we modify the type signatures to use : MonadStates foo :: (MonadState s m, HasFooState s) => m Bool We will have to swap calls to with , calls to with and becomes . We modify in a similar way: get use fooState modify modifying fooState put assign fooState bar bar :: (MonadReader s m, HasBarState s) => m Int and instances: instance HasFooState (Int, String) wherefooState = _1 instance HasBarState (Int, String) wherebarState = _2 We get the combined version to type check. foobar :: State (Int, String)foobar = doflag <- foo if flag thenbarelsereturn 0 Use Case 6: Extendible Exceptions Basing the class on s allows us to have extendible exceptions with . Has Prism MonadError Our class will look like: class HasIdNotFound a where_IdNotFound :: Prism a UUID We then write our functions like: foo :: (HasIdNotFound e, MonadError e m) => m a and can throw our exceptions by calling: throwError $ review _IdNotFound theId For a more complicated variant that requires fewer instances, take a look at this . post A Magical Alternative If you’re like me, you’re probably wondering if there was some magical way to write and without doing any work. There is! We can use (or another similar library). collectImages hashSceneImages [uniplate](https://hackage.haskell.org/package/uniplate-1.6.12/docs/Data-Generics-Uniplate-Data.html) We need to enable and add a to each type. Then our becomes: DeriveDataType deriving (Data) collectImages import Data.Generics.Uniplate.Data collectImages :: Scene -> Set TextcollectImages x = fromList (universeBi x) and our is now: hashSceneImages hashSceneImages :: Scene -> IO ScenehashSceneImages x = transformBiM hashFilePath x The downside to this approach is it indiscriminately collects all values. This is not necessarily what we want (we could make a to make it safer). Another downside is it is slower than a custom traversal class. Text newtype ImageFile = ImageFile Text Conclusion type classes are simple, but they can keep your code well-structured and help you tackle common tasks. Additionally, you might be able to YOLO it with . Has uniplate The repo with more complete examples . here is how hackers start their afternoons. We’re a part of the family. We are now and happy to opportunities. Hacker Noon @AMI accepting submissions discuss advertising & sponsorship To learn more, , , or simply, read our about page like/message us on Facebook tweet/DM @HackerNoon. If you enjoyed this story, we recommend reading our and . Until next time, don’t take the realities of the world for granted! latest tech stories trending tech stories