Developers roadmap

Inspired by developers-roadmap.

Extensions:

{-# LANGUAGE DataKinds #-}
{-# LANGUAGE StandaloneKindSignatures #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE TypeFamilyDependencies #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE NumericUnderscores #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE InstanceSigs #-}
{-# OPTIONS_GHC -Wno-unused-top-binds #-}

Imports

import Control.Monad.Fix (fix)
import Language.Haskell.TH.Syntax (Dec, Quasi, runQ)
main = undefined

Kinds

  • DataKinds - src

    • What is the data type promotion?
      • promote terms to type level like '[1,2,3]
  • Are types with promoted kinds inhabited?

    • inhabited types (types that have at least 1 value) are of kind Type
  • ConstraintKinds - Constraints as first-class citizens

    type Stringy a = (Read a, Show a)
    
  • Symbol - a compile-time string

Functional dependencies

  • Set a relation between types. Make one type correspond to another type

    class (Monad m) => MonadError e m | m -> e where
      throwError :: e -> m a
      catchError :: m a -> (e -> m a) -> m a
    
  • Problem (src):

    we want a MonadReader typeclass where there is only a single instance per m, and we know the env parameter that will be available from each m.

    • Approach 1:
      • MultiParamTypeClasses let us specify explicitly what the env is

      • FunctionalDependencies allow us to constrain ourselves to a single instance.

        newtype PersonReader a = PersonReader { runPersonReader :: Person -> a } deriving Functor
        
        class MonadReader env m | m -> env where
          ask :: m env
        
        instance MonadReader Person PersonReader where
          ask = PersonReader $ \env -> env
        
        instance MonadReader env (Reader env) where
          ask = Reader $ \env -> env
        
        greeting :: PersonReader String
        greeting = do
          person <- ask
          -- Here, derives that `person :: Person`
          -- from `instance MonadReader Person PersonReader`
          -- via fundep `m -> env` and `ask :: m env`
          pure $ show person
        
      • Approach 2:

        • TypeFamilies

          class MonadReader m where
            -- use an associated type
            type Env m
            ask :: m (Env m)
          
          data Person
          newtype PersonReader a = PersonReader (a -> a)
          
          -- `m (Env m)` calculates to `PersonReader Person`
          instance MonadReader PersonReader where
            type Env PersonReader = Person
            ask :: PersonReader (Env PersonReader)
            ask = PersonReader id
          

Laziness

  • Bang patterns

{-# LANGUAGE BangPatterns #-}
addBang :: Int -> Int -> Int
addBang !x !y = x + y

-- equivalent to
addSeq :: Int -> Int -> Int
addSeq x y = x `seq` y `seq` x + y
  • $! - strict application

  • Thunk is an unevaluated expression - src

    • free variables in an unevaluated expr
    • when evaluated, the pointers to it will point to the result
    • a dead thunk is garbage collected
  • Expression forms - src

    • Normal form

      An expression in normal form is fully evaluated, and no sub-expression could be evaluated any further (i.e. it contains no un-evaluated thunks).

    • Weak head normal form

      An expression in weak head normal form has been evaluated to the outermost data constructor or lambda abstraction (the head).

      (1 + 1, 2 + 2)       -- the outermost part is the data constructor (,)
      \x -> 2 + 2
      
  • a `seq` b - eval a to WHNF, return b

  • a `deepseq` b - eval a to NF, return b

  • force b = b `deepseq` b - eval b to NF and return b

    • If we have let a = force b, a is not in NF
    • To get a in NF, we need to !a
  • Thunks, Sharing, Laziness via ghc-viz (available in nixpkgs)

  • safe-exceptions

    • force impure exceptions using tryAnyDeep and NFData.

Fix combinator

ex13 :: [Int] -> Int
ex13 =
  fix
    ( \t c ->
        \case
          (a0 : a1 : as) -> t (c + fromEnum (signum a0 /= signum a1)) (a1 : as)
          _ -> c
    )
    0

-- >>>ex13 [-3,0,2,0,5]
-- 4

File IO

  • There are several representations of text in Haskell - ByteString, Text, String
  • ByteString can contain both human-readable or binary data that mustn't be mixed
  • Also, there are many file encodings. Use UTF-8 to be safe
  • One can encode standard data types into a ByteString using Data.ByteString.Builder
  • LBS reads files in chunks. Can be used for streaming
  • hGet reads a given number of bytes from a handle
  • stdout and stdin are files
  • Can set buffering mode on a handle: hSetBuffering stdout NoBuffering

Debugging

  • Debug.Trace
  • breakpoint - src
    • put breakpoints into an app (see TryBreakpoint)
    • inspect variables visible at a breakpoint
    • freeze other threads (GHC 9.2.x+)

Monoid

  • List comprehension
    • Skip elements
      • On a flag

        _deepClone :: Bool
        _deepClone = True
        
        s1 :: [String]
        s1 = ["--deepClone" | _deepClone]
        
      • On a pattern fail

        catMaybes :: [Maybe a] -> [a]
        catMaybes ls = [x | Just x <- ls]
        

Template Haskell

  • capture haddocks

  • print the structure of an expression

    ex2 :: (Quasi a) => a [Dec]
    ex2 = runQ [d|decl :: Int; decl = 1 + 2|]
    
    -- >>>ex2
    -- [SigD decl_0 (ConT GHC.Types.Int),ValD (VarP decl_0) (NormalB (InfixE (Just (LitE (IntegerL 1))) (VarE GHC.Num.+) (Just (LitE (IntegerL 2))))) []]
    

Higher-Kinded Data

Generics

  • Higher-Kinded Data
  • aeson converts data to generic representation.
    • Its functions for parsing use selector names, modify them via options, then convert to or parse JSON.

QualifiedDo

Effects

Effectful

  • effectful
    • Talk at Lambda
    • Сервер с servant, esqueleto, effectful - YT
    • News site back-end - GH
    • Effects may be pure - runPureEff

String interpolation

Optics

Monad transformer stack

  • Determine the type - SO

UnliftIO

  • Demystifying MonadBaseControl
    • Capture the action’s input state and close over it.
    • Package up the action’s output state with its result and run it.
    • Restore the action’s output state into the enclosing transformer.
    • Return the action’s result.

Handle pattern

  • src

  • Take functions from a given environment, e.g. from ReaderT

Data

GHCJS

Nix

Misc