Learning Haskell

Table of contents

No heading

No headings in the article.

Learning new programming languages influences how you craft out solutions as a developer. Doing all your development with one language will definitely make you a pro in that language but you are more likely to end up as a one trick pony, never able to think outside of the box.

It is also important to point out that picking up a new language whose syntax or paradigm is similar to that of the language you currently know won't do much to expand your capacity as a programmer.

As Will Kurt put it in his book , Haskell is a language that forces you to write safe and functional code and to model your problems carefully. Learning to think in Haskell will make you reason better about abstraction and stop potential bugs in code in any language. You can’t learn enough Haskell to write nontrivial programs and not come away knowing a fair bit about functional programming, lazy evaluation, and sophisticated type systems. This background in programming language theory is not merely beneficial for the academically curious, but serves a great pragmatic purpose as well.

Most books and resources advices even senior developers to approach Haskell with patience and an open mind. Patience because there are lots of concepts you have to learn before you can create basic trivial applications. An open mind because the paradigms of Haskell is so far apart from other programming languages. In this article, I try to highlight some of the differences I've seen in my attaempt to learn Haskell.

  • Haskell uses static typing. Been a compiled language, bugs that only pops up or become obvious during runtime becomes eliminated. But unlike other statically-typed language such as Java, C, or C++, it's possible to write lots of code without your variables and functions making reference to types at all. This is because Haskell makes heavy use of type inference. The Haskell compiler is smart, and can figure out what types you’re using based on the way the functions and variables are used. For example, undeclaredType x = x^2. undeclaredType 2 equals 4 because the compiler correctly figures out the type.
     unknownType :: Int -> Int
  • Unlike most programming languages which have the idea of casting a variable from one type to another, Haskell has no convention for casting types and instead relies on functions that properly transform values from one type to another.
    // python
     num_str = "123"
     num_int = int(num_str)

    // haskell
     intToString :: Int -> String
     intToString number = show (number)
  • Functions play a major role in Haskell, as it is a functional programming language and like other languages, Haskell has rules guiding its use. The most important ones are; 1. Functions only starts with a lowercase or _ 2. Unlike other languages, there's no need to explicitly tell functions to return a value because Haskell's function by design must return a value 3. Functions must an argument [f(x) = y] (Haskell's functions take only one argument). But that doesn't mean you can't have something like this.
      matrix :: String -> Int -> Int -> (String, Int, Int)
      matrix name speed strength = (name, speed, strength)

      matrix "Leo" 24 15 = ("Leo", 24, 15)

Desugaring this, it shows that under the hood, Haskell uses series of nested lambda functions (curry) to produce what looks like a multi argument function.

      matrix = (\name -> (\speed -> (\strength -> (name, speed, strength)))

      (((matrix "Leo") 24) 15)
  • Immutability of variable. Unlike most languages where variables can change state or be reassigned, Haskell embraces referential transparency. This strictness leads to writing a code that can be easy to predict or debug. Ability to reassign values in other languages is important because code are read from top to bottom and variables are declared before they’re used. In Haskell, because of referential transparency, this isn’t an issue. Running a similar code like this in other languages will throw an error ["difference not defined" or "difference not declared"]
     val :: Int -> Int -> Int

     val initial remainder = if remainder > 0
                             then difference
                             else 0

        where difference = initial – remainder
  • OOP. It can be argued that Haskell like other functional language does not support object oriented programming. But Haskell uses closures (captures values in a lambda function) to a produce a somewhat similar result. Not as direct or straightforward like other languages such as python but still it produces object manipulation.
robot (name, attack, hp) = \message -> message (name, attack, hp)

killerRobot = robot ("Kill3r", 25, 200)

// helper functions
name (n, _, _) = n
attack (_, a, _) = a
hp (_, _, hp) = hp

getName aRobot = aRobot name
getAttack aRobot = aRobot attack
getHp aRobot = aRobot hp

setName aRobot newName = aRobot (\(n, a, h) -> robot (newName, a , h))
setAttack aRobot newAttack = aRobot (\(n, a, h) -> robot (n, newAttack, h))
setHp aRobot newHp = aRobot (\(n, a, h) -> robot (n, a, newHp))

printRobot aRobot = aRobot (\(n, a, h) -> n ++ 
                                          " attack:" ++ (show a) ++ 
                                          " hp:" ++ (show h))