Skip to content

giocosmiano/haskell-programming

Repository files navigation

This is my personal repository learning Haskell from Haskell Programming

  • First, this is my personal journey learning Haskell therefore any mistakes on concepts and/or chapter exercises are my own.

  • Second, I came to this Haskell journey because of my curiosity in FP that began mid-2016 when I was working on a UI story/task in Angular and used these JS libraries, RamdaJS, RxJS and ReduxJS, that are primarily developed with FP concepts in mind, such as immutability, composition, high-order function etc.

    Since then my Haskell journey has been on-and-off, until the fall season of 2018 that I picked up this book to learn. I can say that the authors did an excellent job writing this, shout-out to them. I'm now able to demystify Haskell, bit by bit, while having fun working on chapter exercises as the authors made me think, connect the dots and perform diagram chasing.

Notes about my journey in working through chapter exercises

List of reminders to myself

newtype ComposeType f g h a = ComposeType { getComposeType :: f (g (h a)) } deriving (Eq, Show)

outerInner :: MaybeT (ExceptT String (ReaderT String (StateT String IO))) Int

newtype ActionT e m a =
  ActionT
  { runAM
    :: ExceptT
         (ActionError e)
         (ReaderT ActionEnv
           (StateT ScottyResponse m))
         a
  }
  deriving ( Functor, Applicative, MonadIO )
  • When transforming a structure, NOT the value inside it, such as List-to-Maybe, use Natural Transformation i.e.
{-# LANGUAGE RankNTypes #-}

type Nat f g = forall a . f a -> g a
Haskell λ > :t liftIO
liftIO :: MonadIO m => IO a -> m a

Haskell λ > :t lift
lift :: (Monad m, MonadTrans t) => m a -> t m a
newtype MaybeT m a = MaybeT { runMaybeT :: m (Maybe a) }

instance (Functor m) => Functor (MaybeT m) where
  fmap :: (a -> b) -> MaybeT m a -> MaybeT m b

instance (Applicative m) => Applicative (MaybeT m) where
  pure :: Applicative m => a -> MaybeT m a
  (<*>) :: MaybeT m (a -> b) -> MaybeT m a -> MaybeT m b

instance (Monad m) => Monad (MaybeT m) where
  (>>=) :: MaybeT m a -> (a -> MaybeT m b) -> MaybeT m b

instance (Foldable m) => Foldable (MaybeT m) where
  foldMap :: (Monoid mn, Foldable m) => (a -> mn) -> MaybeT m a -> mn

instance (Traversable m) => Traversable (MaybeT m) where
  traverse :: Applicative fa => (a -> fa b) -> MaybeT m a -> fa (MaybeT m b)

Conclusion to my Haskell journey

  • Finally, after months of reading, more-and-more readings, lots of trial-and-error in GHCi, and countless nights/weekends working through chapter exercises, I can now celebrate completing this massive technical book. I don't consider myself reaching the ivory tower nor achieved some programming enlightenment but rather this experience gave me substantial knowledge to start the next chapter of my journey towards working in real-world Haskell applications.

Chapter 1 - All You Need is Lambda

Haskell λ > let addAndMultiply = \x -> \y -> \z -> x + y * z

Haskell λ > addAndMultiply 3 5 7
38
  • Introduction to the following Haskell subjects
    • Function and evaluation
    • Infix operators
    • Associativity and precedence
    • Expression components such as let and where

Chapter 3 - Strings

  • Printing strings
  • Concatenation functions
  • Built-in data types
  • Integral vs Int, Integer and Word
  • Bool and comparing values
  • Tuples and Lists
  • In OOP, such as Java, this is similar to defining interface. Haskell data types doesn't have any function associated with it unlike Java wherein functions are automatically inherited from java.lang.Object.
  • Type classes Eq, Num, Ord, Enum, Show, Read
  • Type class inheritance
  • Bottom
  • Factorial! and Fibonacci numbers
  • Algebraic datatypes
  • Data and type constructors
  • Type constructors and kinds
  • Data constructors and values
  • Data constructor arities
  • newtype, Sum types, Product types
  • Function type is exponential
  • Higher-kinded data types
  • Nothing, or Just Maybe
  • Either left or right, but not both
  • higher-kindedness
  • Modules
  • Making packages with Stack
  • Building a project
  • Algebra and Laws

  • use of newtype

    • To signal intent: using newtype makes it clear that you only intend for it to be a wrapper for the underlying type. The newtype cannot eventually grow into a more complicated sum or product type, while a normal datatype can

    • To improve type safety: avoid mixing up many values of the same representation, such as Text or Integer

    • To add different type class instances to a type that is otherwise unchanged representationally, such as with Sum and Product

Haskell λ > import Data.Monoid

Haskell λ > Sum 3 <> Sum 5 <> Sum 7
Sum {getSum = 15}

Haskell λ > Product 3 <> Product 5 <> Product 7
Product {getProduct = 105}
  • Laws

  • IO Functor

  • Functors are unique to a datatype

  • Functor is a way to apply a function over or around some structure that we don’t want to alter. That is, we want to apply the function to the value that is “inside” some structure and leave the structure alone.

Haskell λ > newtype MyName = MyName { getMyName :: String } deriving (Eq, Show)

Haskell λ > MyName <$> Just "gio"
Just (MyName {getMyName = "gio"})
Haskell λ > fmap (const 3)  [Just "hello world"]
[3]

Haskell λ > (fmap . fmap) (const 3)  [Just "hello world"]
[Just 3]

Haskell λ > (fmap . fmap . fmap) (const 3)  [Just "hello world"]
[Just [3,3,3,3,3,3,3,3,3,3,3]]
  • Laws

  • Functor vs Applicative

  • ZipList Monoid

  • Applicatives are monoidal functors. The Applicative type class allows for function application lifted over structure (like Functor). But with Applicative the function we’re applying is also embedded in some structure. Because the function and the value it’s being applied to both have structure, we have to smash those structures together. So, Applicative involves monoids, like mappend, and functors.

Haskell λ > (*) <$> Just 3 <*> Just 5  
Just 15

Haskell λ > Just (*3) <*> Just 5 
Just 15
  • Laws

  • do syntax and monads

  • Application and composition

  • Think of Monads as another way of applying functions over structure, with the ability of the function to alter the structure, something we’ve not seen in Functor and Applicative. Monad can inject more structure. However, it has the ability to flatten those two layers of structure into one is what makes Monad special. And it’s by putting that join function together with the mapping function that we get bind, also known as >>=.

  • The Monad type class is essentially a generalized structure manipulation with some laws to make it sensible. Just like Functor and Applicative.

  • Sample Monad that uses join implicitly to flatten the structure within structure

Haskell λ > getLine >>= putStrLn
hello world
hello world

Haskell λ > :t getLine >>= putStrLn
getLine >>= putStrLn :: IO ()
  • As oppose to a Functor
Haskell λ > putStrLn <$> getLine
hello world

Haskell λ > :t putStrLn <$> getLine
putStrLn <$> getLine :: IO (IO ())
  • To turn the Functor into a Monad, use the join explicitly to flatten the structure within structure
Haskell λ > import Control.Monad

Haskell λ > join $ putStrLn <$> getLine
hello world
hello world

Haskell λ > :t join $ putStrLn <$> getLine
join $ putStrLn <$> getLine :: IO ()
  • Monoid
  • Functor
  • Applicative
  • Monad
  • Revenge of the monoids

  • Foldable is a type class of data structures that can be folded to a summary value.

Haskell λ > import Data.Functor.Constant

Haskell λ > foldr (*) 3 (Constant 5)
3

Haskell λ > foldr (*) 1 [1,2,3,4,5]
120
  • Laws

  • traverse, sequenceA

  • Traversable allows you to transform elements inside the structure like a Functor, producing Applicative effects along the way, and lift those potentially multiple instances of Applicative structure outside of the traversable structure. It is commonly described as a way to traverse a data structure, mapping a function inside a structure while accumulating the applicative contexts along the way.

  • traverse is mapping a function over some embedded value(s), like fmap, but similar to flip bind, that function is itself generating more structure. However, unlike flip bind, that structure can be of a different type than the structure we lifted over to apply the function. And at the end, it will flip the two structures around, as sequenceA did.

Haskell λ > import Data.Functor.Identity

Haskell λ > traverse (Identity . (+1)) [1, 2, 3]
Identity [2,3,4]

Haskell λ > runIdentity $ traverse (Identity . (+1)) [1, 2, 3]
[2,3,4]
  • sequenceA is flipping two contexts or structures. It doesn’t by itself allow you to apply any function to the a value inside the structure; it only flips the layers of structure around.
Haskell λ > sequenceA [Just 3, Just 2, Just 1]
Just [3,2,1]
  • On the SkiFreeExercises, I'm able to write the Functor, Applicative, Foldable and Traversable instance of S structure, where fa is also a functorial structure. The Monad instance, however, is failing on the right identity law running the quickBatch check from quickCheck. I still have to figure out how-to fix this correctly.
data S fa a = S (fa a) a deriving (Eq, Show)

-- TODO: fix the implementation of `monad` S
instance (Monad fa) => Monad (S fa) where
  return = pure
--  (S fa a) >>= f = S (fa >>= f) (f a)
  (S fa a) >>= f =
    let S fa' a' = f a
    in  S fa' a'
monad laws:
  left  identity: +++ OK, passed 500 tests.
  right identity: *** Failed! Falsifiable (after 1 test): S [] 0
  associativity:  +++ OK, passed 500 tests.
data S fa a = S (fa a) a deriving (Eq, Show)

newtype ST fa a = ST { runST :: fa a } deriving (Eq, Show)
  • Breaking down the Functor of functions

  • Functions have an Applicative too

  • Monad of functions

  • Reader is a way of stringing functions together when all those functions are awaiting one input from a shared environment. The important intuition is that it’s another way of abstracting out function application and gives us a way to do computation in terms of an argument that hasn’t been supplied yet. We use this most often when we have a constant value that we will obtain from somewhere outside our program that will be an argument to a whole bunch of functions. Using Reader allows us to avoid passing that argument around explicitly.

Haskell λ > (+) <$> (+3) <*> (*5) $ 7
45

Haskell λ > (+) <$> (runReader $ Reader (+3)) <*> (runReader $ Reader (*5)) $ 7
45
  • State newtype

  • Random numbers

  • The State type in Haskell is a means of expressing state that may change in the course of evaluating code without resort to mutation. The monadic interface for State is more of a convenience than a strict necessity for working with State.

Haskell λ > import Control.Monad.State

Haskell λ > (runState $ get >> put 5 >> return 9 >> modify (+3) >> return 12 >> modify (*5) >> return 9001) 3
(9001,40)
  • Parser

  • Parser combinator

  • Haskell’s parsing ecosystem

  • Marshalling from an AST to a datatype

  • The exercises on this chapter has a very substantial resources that really jogged my brain.

  • Composing types

    • Essentially, a structure that has multi-layered structures in it

    • Functors and Applicatives are both closed under composition, which means we can compose two functors (or two applicatives) and return another functor (or applicative, as the case may be). This is not true of monads, however; when we compose two monads, the result is NOT necessarily another monad.

newtype ComposeType f g h a = ComposeType { getComposeType :: f (g (h a)) } deriving (Eq, Show)
  • Monad Transformers

    • Because we can't compose two monads together and create a new Monad, we need Monad Transformer to reduce the polymorphism and get concrete information about one of the monads that we’re working with, to make the join happen
  • IdentityT

  • Good chapter in preparation for Monad Transformer.

  • Laziness, Non-Strict

  • Thunk, Sprint

  • Forcing Sharing

  • Bang Patterns

  • Strict and StrictData

  • Exceptions, try/catch, throw/throwIO, custom exception, async exception

Referenced frameworks/libraries

For further reading

IDE/IntelliJ Plugins

$ stack exec -- ghc --version
The Glorious Glasgow Haskell Compilation System, version 8.0.2

$ stack exec -- ghc-mod --version
ghc-mod version 5.8.0.0 compiled by GHC 8.0.2

$ stack install scotty

$ stack install pandoc

$ stack exec -- pandoc --version
pandoc.EXE 2.2.1
Compiled with pandoc-types 1.17.5.4, texmath 0.11.1.2, skylighting 0.7.5
Default user data directory: C:\Users\Gio\AppData\Roaming\pandoc
  • Creating executable file (make sure there's a Main module)
$ cd "C:\Users\Gio\Documents\_haskell\haskell-programming\ch30-when-things-go-wrong"

$ stack ghc -- WritingWithException.hs -o WritingWithException.exe
[1 of 1] Compiling Main             ( WritingWithException.hs, WritingWithException.o )
Linking WritingWithException.exe ...

$ ./WritingWithException.exe
wrote to file
  • Running the script without creating an executable file. Can't create an executable without Main module.
$ cd "C:\Users\Gio\Documents\_haskell\haskell-programming\ch29-io"

$ stack VigenereCipherExercises.hs -e ALLY VigenereCipherExercises.hs           VigenereCipherExercises.encrypt.log

$ stack VigenereCipherExercises.hs -d ALLY VigenereCipherExercises.encrypt.log  VigenereCipherExercises.decrypt.log

$ stack ghc -- VigenereCipherExercises.hs -o VigenereCipherExercises.exe
<no location info>: error:
    output was redirected with -o, but no output will be generated
because there is no Main module.
$ stack path

$ stack exec env

$ stack ghci

$ stack unpack scotty

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published