Haskell is a statically typed language- famous for cussing Ocaml’s lack of purity- whose entire presence on Tiobe index consists of people asking “Why should I learn Haskell?” on stackoverflow. Haskell defining feature is monads its type system (however, make no mistake: there are more advanced ones, but Haskell got more web scale frameworks than all of those combined). The nice thing about good type systems is that they force you to see everything in term of types. This statement might seem tautological, but once you get rid of weak Java-like static typing and can spot even the type of types- ahem, kinds- things start to make sense in a sort of organized, Category Theory way, so that you can ditch superfluous things like documentation, as you have all types nicely displayed above the definition of stuff. But what does such type system have that makes it so good and advanced?, may ask you. As always, let’s build leg strength starting up from easy steps:
- Existentially quantified types
In Haskell, even the most innocent looking functions hold key features. Consider the identity function, defined as:
id a = a
Its type, however, reads:
id :: forall a . a -> a
Yay, polymorphic types for free, without silly angles! Oh, wait, those are universal types. Can we tone down the ⊥s?
data ShowBox = forall s. Show s => SB s
instance Show ShowBox where
show (SB s) = show s
heteroList :: [ShowBox]
heteroList = [SB (), SB 5, SB True]
See? With state-of-art types we can crudely construct the heterogeneous lists present in every single dynamic language out-of-the-box. So forth, if we turn on the nice extension RankNTypes,
{-# LANGUAGE RankNTypes #-}
runST :: forall a. (forall s. ST s a) -> a
we also can have variables like virtually every existing language! Take that, LISP macros!
- Clarity
Another thing we might say to Haskell’s favor is its no nonsense philosophy. Who never walked down the street and thought “Man, a fixed pointer combinator is all I need”!?
fix :: (a -> a) -> a
fix f = let x = f x in x
While other languages, such as C++ or Python, use fancy (and overrated) constructs like assignment or while loops, Haskell features only intuitive, down-to-earth practicalities, e.g., free monads, yoneda lemmes, type level recursion and, of course, several clear ways to do streaming IO. I wish they had showed me recursion schemes with catamorphisms when I first learned how to program.
- Type families
Type families solves those problems you otherwise wouldn’t have with simpler languages. The recommend approach is to use them together with type level literals. Let’s see an example:
{-# LANGUAGE TypeFamilies, MultiParamTypeClasses, FlexibleContexts #-}
class (Show pokemon, Show (Move pokemon)) => Pokemon pokemon where
data Move pokemon :: *
pickMove :: pokemon -> Move pokemon
data Fire = Charmander | Charmeleon | Charizard deriving Show
instance Pokemon Fire where
data Move Fire = Ember | FlameThrower | FireBlast deriving Show
pickMove Charmander = Ember
pickMove Charmeleon = FlameThrower
pickMove Charizard = FireBlast
data Water = Squirtle | Wartortle | Blastoise deriving Show
instance Pokemon Water where
data Move Water = Bubble | WaterGun deriving Show
pickMove Squirtle = Bubble
pickMove _ = WaterGun
data Grass = Bulbasaur | Ivysaur | Venusaur deriving Showrecursion schemes
instance Pokemon Grass where
data Move Grass = VineWhip deriving Show
pickMove _ = VineWhip
printBattle :: String -> String -> String -> String -> String -> IO ()
printBattle pokemonOne moveOne pokemonTwo moveTwo winner = do
putStrLn $ pokemonOne ++ " used " ++ moveOne
putStrLn $ pokemonTwo ++ " used " ++ moveTwo
putStrLn $ "Winner is: " ++ winner ++ "\n"
class (Show (Winner pokemon foe), Pokemon pokemon, Pokemon foe) => Battle pokemon foe where
type Winner pokemon foe :: *
type Winner pokemon foe = pokemon
battle :: pokemon -> foe -> IO ()
battle pokemon foe = do
printBattle (show pokemon) (show move) (show foe) (show foeMove) (show winner)
where
foeMove = pickMove foe
move = pickMove pokemon
winner = pickWinner pokemon foe
pickWinner :: pokemon -> foe -> (Winner pokemon foe)
instance Battle Water Fire where
pickWinner pokemon foe = pokemon
instance Battle Fire Water where
type Winner Fire Water = Water
pickWinner = flip pickWinner
instance Battle Grass Water where
pickWinner pokemon foe = pokemon
instance Battle Water Grass where
type Winner Water Grass = Grass
pickWinner = flip pickWinner
instance Battle Fire Grass where
pickWinner pokemon foe = pokemon
instance Battle Grass Fire where
type Winner Grass Fire = Fire
pickWinner = flip pickWinner
main :: IO ()
main = do
battle Squirtle Charmander
battle Charmeleon Wartortle
battle Bulbasaur Blastoise
battle Wartortle Ivysaur
battle Charmeleon Ivysaur
battle Venusaur Charizard
Move over simple dynamic dispatch (you, SmallTalk) or no mental overhead dynamic typing- we can play type level Pokemon.
Summing up, Haskell is de facto practical man’s language. While its use is best advised where cheap PHP hosting is currently employed, its advanced type system can help you leverage all kinds of abstract nonsense.