Skip to content

Haskell

Kevin Kredit edited this page Apr 22, 2020 · 8 revisions

Notes and lessons from the Haskell programming language.

Language Features

Data

Data

  • Immutable

Types

  • Static
  • Strong
  • Inferred
    • Can determine in GHCi with :t var
    • Can specify for variables and funcitons with expression :: Type
  • Lists
    • Dynamic
    • Strings are lists of chars
  • Tuples
    • Basically structs
    • A 'pair' is a 2-tuple
  • Has algebraic data types
  • Record syntax and deriving prevent the need for a lot of boilerplate code
    • Getters and setters
    • Basic operations -- print, compare, order, etc
  • Polymorhpism via type paramaters (e.g. how Maybe is a type constructor and Nothing is a polymorphic data type)
    • "Using type parameters is very beneficial, but only when using them makes sense. Usually we use them when our data type would work regardless of the type of the value it then holds inside it"
  • Types can be aliased (i.e. typedef'd) using type
  • Can be recursive
  • Type "kind"s are like type types; they can also be curried

Variables

  • Traditionally single characters, when feasible
  • Since data is immutable, you don't think of them as variables so much as symbols

Structure and Control

Structure

  • Common patterns
    • Write very small functions that compose together
    • Create sets and apply filters till have solution
  • Everything is a statement (where a 'statement' is that which returns something)
  • Modules have relatively strict, though logical file organization and naming patterns

Execution

  • Purely functional (no side-effects)
  • Lazy (don't evaluate until needed)
  • Order of operations is expected, with function applications highest

Functions

  • Syntax is funcName arg1 arg2
  • Definition is funcName arg1 arg2 = ...
  • Function types can also be inferred
  • Functions may operate on on typeclasses, so may be generic
  • Recursion
    • Important to programming in Haskell
    • Fits will with pattern matching and guards
    • Also well suited to lazy evaluation--can recur infinitely without issue, as the runtime will only execute as much as is needed
    • Leads to concise, elegant solutions
  • Partial application and currying make for quickly buildable and composable functions
    • Hard to explain, see here
    • Maps, filters, and folds are extremely common and powerful applications
  • Lambdas \ are anonymous functions defined inline
  • $ is a function that applies functions; is right-associative
  • Functions are composed with .: funcA . funcB arg is the same as funcA (funcB arg))

Syntax in Functions

  • Pattern matching
    • Offer multiple function implementations based on a patter, and the runtime (or compiler, when possible?) will select the first matching definition
    • Allows for elegant recursion, lack of reliance on if-then-elses
    • Can be used to look into data structures
      addVectors :: (Num a) => (a, a) -> (a, a) -> (a, a)
      addVectors (x1, y1) (x2, y2) = (x1 + x2, y1 + y2)
  • Guards
    • Similar to pattern matching, but checks for conditions, not patterns
    • End with otherwise case
    • Can specify pattern-local (guard-set-global) names using where
    • Can specify gaurd-local names using let name = ... in ...
      • lets can actually be used anywhere
  • Case statements
    • Pattern matching can be used anywhere (not just at the function-definition level)

Typeclasses

  • Similar to "interfaces"
  • Types can belong to a class if they implement all the methods required for that class
  • Can be inhereted

Error Handling

  • The runtime can generate exceptions
  • Errors generated with error "Explanation"
  • Errors can are often communicated using the type constructor Either
    • someFunc :: Int -> Int -> Either String Int
    lockerLookup :: Int -> LockerMap -> Either String Code
    lockerLookup lockerNumber map =
        case Map.lookup lockerNumber map of
            Nothing -> Left $ "Locker number " ++ show lockerNumber ++ " doesn't exist!"
            Just (state, code) -> if state /= Taken
                                    then Right code
                                    else Left $ "Locker " ++ show lockerNumber ++ " is already taken!"

Cool Things

  • Ranges: interpolated lists
  • List comprehensions: generated lists using a generator function, bindings, and predicates
  • "You do computations in Haskell by declaring what something is instead of declaring how you get it."

Tooling

  • Install process is trivial on Ubuntu
  • Haskell "stack" seems to be the package manager for Haskell modules
    • Install process is broken! link
  • Debugging is not intuitive coming from imperative languages, though trace is quite helpful
    • Example:
      myfun a b | trace ("myfun " ++ show a ++ " " ++ show b) False = undefined
      myfun a b = ...
    • See wiki page

Feel

Really cool. I love the support of the type system. It feels robust. Hard to read though; feels like you have to be at the level of the author in order to read the author's code (as opposed to Go, which is easy to understand even if you've never written it). You spend a lot of time getting your data types right. With curried functions, wheres and lets, it's really easy to not repeat yourself.

Writing a program in Haskell feels like taking a "Data Structures and Algorithms" class. You are forced to think extremely clearly about your data types and the computational patters you're using to manipulate them. There is value in being able to whip out an imperative program that says "do this and then that and then that," but there is also value in breaking a problem down to its core and encoding the problem in its fundamental patterns.

Clone this wiki locally