Don't worry: we're getting there!

Welcome to the second part of reading Haskell Programming from First Principles. This time around we finally see some actual Haskell code. Sort of. It's mostly just a tour of Haskell's basic syntax, along with a brief introduction to the REPL and how that works. As such, let's do a quick recap of the most essential parts.

There are quite a few references to /the REPL/ in this article. /REPL/ is short for /read eval print loop/ and is a command line interpreter for a programming language. In our case, GHC's REPL is invoked by either the ~ghci~ or the ~stack ghci~ command, depending on whether you're using GHC directly or through Stack.

General notes

Let's start with some general notes about how Haskell code works, shall we?

First off, Haskell is a whitespace-sensitive language. Much like in Python, the indentation of your code matters. A lot. However, unlike in Python, indentation doesn't come in multiples of four. Or two. Or any number, really. Instead, it's dependent on the code (and as such, tabs are out). As the book states:

The basic rule is that code that is part of an expression should be indented under the beginning of that expression, even when the beginning of the expression is not at the leftmost margin. Furthermore, parts of the expression that are grouped should be indented to the same level.

To make that clearer, let's have an example:

  calc x =
    let y = x ^ 2
        z = x - 1
     in x + y + z

Notice how all the variable assignments line up and how the in block (or line in this case) is indented one space more than the let block. Like many languages, Haskell has multiple valid ways to structure your code, so just find a style you like and stick to it. The key thing is: don't freak out if the indentation isn't what you expect at first.

Point two: capitalization matters. Haskell is quite strict about this and won't compile unless you follow the rules. Functions and variables start with lowercase letters (and conventionally use camelCase for longer identifiers), while types and type constructors (e.g. Bool and its variants True and False, Int, etc.) are captitalized (and use what's sometimes known as PascalCase).

Point three: Haskell is 'lazy', or 'lazily evaluated' or 'non-strict'. What this means is that Haskell won't evaluate anything until it really needs to. This is why we can work with infinite lists with no problems---unless you try and consume the whole thing, of course.

Remember when we talked about beta normal form and beta reduction in the last chapter? Haskell doesn't evaluate everything to normal form by default. Instead, it evaluates to what is known as weak head normal form. Because of referential transparency, it knows that it can compute everything on demand, so until a value is required from an expression, the expression will be left unevaluated. Imagine it's a pointer to an expression instead of a value, which can be lazily evaluated when it's first used.

Finally, on comment syntax: Haskell single-line comments start with a double dash (a literal --, not the Nintendo kart racing kind), while multiline comments are put between {- and -}.

Functions

Now, let's have a closer look at function definitions. A function definition is made up of the name of the function, the parameters (separated by whitespace), an equals sign, and the expression that is the body. Example:

add x y = x + y

This function takes two arguments---~x~ and y---and returns the sum. Notice that we don't say anything about the types here. The compiler is smart enough to figure that out. If we use the REPL and the :info (or :i) command to describe this function, it tells us that the type of the expression is: Num a => a -> a -> a. It's automatically generic for all numeric types (well, all instances of the typeclass Num, but we're getting ahead of ourselves). Neat!

We won't be looking any deeper into type annotations and function signatures for the time being. They're not covered in this chapter, but do show up later; so if all the arrows in the the type signature above confuse you: don't worry.

It's also worth mentioning that, much like lambda calculus, Haskell uses curried functions. If you forgot what that means, the authors describe it like this: "In Haskell, when it seems we are passing multiple arguments to a function, we are actually applying a series of nested functions, each to one argument." Because we return a new function for each argument we apply, we can assign the result of a partially applied function to a variable and save it for later, just like we did with the lambda expressions last time.

Operators and infix functions

In addition to regular functions, Haskell also has operators. These are a mix of what you'd expect from any programming language (+, -, *, /, etc.) and more exotic ones (>>=, <$>). In fact, Haskell lets you define your own operators too. This might sound unconventional to some, but when you realize that a binary operator is really just a binary function placed in between its arguments, it makes a lot of sense.

See, binary operators are what we call infix, which means that they go in between their arguments. But operators aren't the only things that can be infix: Any binary function in Haskell can be used as an infix function if you wrap it in backticks (e.g. div 2 2 becomes 2 `div` 2).

Using partially applied infix operators is called sectioning. It's briefly mentioned in the book and there's also a very easily understood article on the Haskell wiki on it, so let's use an extract from the latter to clarify:

Essentially, you only give one of the arguments to the infix operator, and it represents a function which intuitively takes an argument and puts it on the "missing" side of the infix operator. - ~(2^)~ (left section) is equivalent to ~(^) 2~, or more verbosely ~\x -> 2 ^ x~ - ~(^2)~ (right section) is equivalent to ~flip (^) 2~, or more verbosely ~\x -> x ^ 2~

When working with operators, it's important to keep in mind the precedence rules. Most of us probably know that multiplication has higher precedence than addition, for instance---which is why $1+3*5$ is $16$ and not $20$---but it's not always immediately obvious for all operators. In Haskell, precedence is defined as a number between 0 and 9, where higher numbers denote higher precedence. If you ever wonder about a specific operator, you can use the REPL's :info command to get information about precedence for any operator (e.g. :i (*)).

Most of the operators introduced in this chapter are pretty self-explanatory---don't worry, the weird ones I referenced before aren't mentioned---but there is one that is singled out into its own little section: $. This operator is a little special in that it has a precedence of 0 and that it is defined as f $ a = f a.

What?

Indeed, it doesn't actually do anything, but it's all about the precedence. This allows us to use this operator to write an expression (a) after it that will get fed into the expression before it (f).

Err ... okay? So what's the point?

Oh, it helps us avoid parentheses and a lot of nesting of functions. It's very useful for composition. A contrived example would be:

  -- is the result of the calculation even?

  -- without the $ symbol
  even (2 + 2)

  -- with the $ symbol
  even $ 2 + 2

It probably still seems pretty pointless, but you'll grow to appreciate it as we progress further. Trust me.

Assigning variables in expressions

As we saw in the first section, you can assign variables with a let expression, but there is another way too: the where declaration.

let introduces an expression, and can thus be used wherever you can use expressions, while where is a syntactic construct, and is only valid in certain parts of the code. If you're in a function, though, both will serve you just fine. These two functions evaluate to the same result:

letFunction x =
  let y = x + 1
   in y + x

whereFunction x = y + x
  where
    y = x + 1

Apart from mentioning that a let expression is just that, an expression, and that where is a declaration, the book doesn't go into any more details of when to prefer one over the other, so neither will we. However, if you're interested, I encourage you to check out the Let vs. Where article on the Haskell wiki.

Definitions

Definition time! This is a selection of the definitions from the end of the chapter, leaving out the definitions not related to what we have touched on.

Expression
Anything that conforms to the Haskell syntax and that can be reduced to a result. In theory, even constants that can not be reduced (such as the number $1$) are expressions, but these are generally referred to as values in common parlance.
Value
A value is an expression that can not be reduced further.
Function
A mathematical object that maps a set of inputs (the domain) to a set of outputs (the codomain). A transformer of values. It might be interesting to note that the book also mentions that functions can be described as a list of ordered pairs of their inputs and corresponding outputs: Example: A function (^2) (a simple square function) defined for natural numbers would start with the entries (0, 0), (1, 1), (2, 4), (3, 9)
Infix notation
A style of notation where the operator is placed between the operands.
Operators
In Haskell: Functions that are infix by default. Must use symbols only.

Conclusion and next time

Not bad! We've seen some actual code this time and played around a bit at the REPL. We learned a bit about the basic syntax and about how operators and infix notation work, about sectioning and the $ operator.

Next time, we'll be looking at the String type and how it is implemented in Haskell.

See you then!


The text 'Building a custom NixOS installer' over blurred lines of source code. There is a Nix Snowflake in the bottom right corner.

It's easier than you'd think!

Just a few days ago, I finally brought my old MacBook Pro (mid-2012 retina) back from the dead and, naturally, wanted to install Linux on it. My main distro being NixOS, that was the obvious choice. However, when I booted into the installer, I found that I had no networking available. After some digging, I found that it was because it needs proprietary Broadcom wi-fi drivers to connect to anything.

The standard NixOS installer is very dependent on an internet connection to download packages and configure your system, so how do you deal with that? Without a wi-fi connection (and no ethernet ports), it's not like I can just download the drivers either. Luckily, NixOS lets you build your own custom installer ISOs with a selection of packages available.

I found this GitHub issue that discusses solutions and provides a custom image you can use. The problem is that the provided image is for NixOS 17.03, which uses an older version of Nix (pre-2.0). At first I figured I could just perform the update during the installation or after, but due to the Nix version bump, I was unable to upgrade. Obviously, I had to take matters into my own hands.

Now that I knew I could build my own custom ISOs, I went looking for documentation on it and found the section in the manual and the wiki article. They're both pretty short, but that's because they don't need to be any longer: It's surprisingly easy and straightforward.

Simply create a Nix file that imports their installers and specifiy what packages you want available and any changes you want to make to the system.

The base config file they provide is this:

# This module defines a small NixOS installation CD.  It does not
# contain any graphical stuff.
{config, pkgs, ...}:
{
  imports = [
    <nixpkgs/nixos/modules/installer/cd-dvd/installation-cd-minimal.nix>

    # Provide an initial copy of the NixOS channel so that the user
    # doesn't need to run "nix-channel --update" first.
    <nixpkgs/nixos/modules/installer/cd-dvd/channel.nix>
  ];
}

We can extend this to add our proprietary drivers and even additional packages to have available in the installer. So if you prefer the Fish shell to Bash or want to have access to Git? Yeah, we can do that.

  {config, pkgs, ...}:
  {
    imports = [
      <nixpkgs/nixos/modules/installer/cd-dvd/installation-cd-minimal.nix>
      <nixpkgs/nixos/modules/installer/cd-dvd/channel.nix>
    ];

    # configure proprietary drivers
    nixpkgs.config.allowUnfree = true;
    boot.initrd.kernelModules = [ "wl" ];
    boot.kernelModules = [ "kvm-intel" "wl" ];
    boot.extraModulePackages = [ config.boot.kernelPackages.broadcom_sta ];

    # programs that should be available in the installer
    environment.systemPackages = with pkgs; [
      fish
      git
    ];
  }

Building the image is a simple command. Assuming your file is called iso.nix:

nix-build '' -A config.system.build.isoImage -I nixos-config=iso.nix

And that's it. Now, just flash it onto a drive or burn it onto a disc and you're off to the races.


Before rounding out this post, there's a few things I think it's worth to be aware of, even if we won't look too much into them:

  • The wiki states that you don't need an internet connection when using a custom ISO with packages, which makes me think that if you specify in this config all the packages you'll need (for your final install), you won't need to download them again when actually installing.
  • nix-build will build the image based on your system's nixos Nix channel. I use the unstable branch, so it built an image based on a NixOS 19.09 preview. You can of course change channels in the installer if you want to use a different base for the install (though this would definitely require internet access).
  • From what I understand, you need a NixOS system to build one of these images. So if you need one of these but don't run NixOS already, it seems you're locked into finding someone who could build the image for you. This isn't great for onboarding new people, and you'd think that it'd suffice with just the Nix package manager---honestly: it might. I don't know---but the documentation talks specifically about building using "an existing working NixOS installation". As was pointed out to me on Reddit, you need the Nix package manager to build one of these images, but not necessarily a NixOS system. However, it was also mentioned that it does probably have to be a Linux system, so Nix on macOS likely wouldn't work.

And with that: goodbye!


The VS Code and Emacs logos side by side. The Emacs logo is surrounded by hearts.

It was about a year ago. My life was good. I had things in order (mostly). And most importantly: I had an editor I liked. VSCode and I had been going steady for about a year. With its excellent support for Python and JS---which was most of my work in those days---I rarely ever needed to venture into new territory. It felt good. I was happy.

I was happy!

Then it all came crashing down. A good friend and I decided to hang out and program one Saturday. The sky was a clear blue, the temperature high, and summer was just around the corner. We got into his office, sat down, and started setting up. That's when it happened. Completely out of left field:

--- So I found this cool thing the other day. It's called Spacemacs.

Spacemacs?

--- Yeah, it's Emacs, but with "evil mode". So you can get your Vim bindings!

It's true. I had been seduced into the deep, dark depths of Vim about a year prior, and had never found my way out. Never tried, really. I had let it consume me, and there was no way I'd let it go. Not for the world. Of course I had been using the VSCode Vim plugin, but while it had given me most of the functionality I wanted, I did sometimes find myself longing for something more. So I thought I ought to at least give this Spacemacs thing a go.

With one eyebrow slightly cocked, I went ahead and downloaded Spacemacs. I had tried to run Emacs previously, but had been met with that sheer, white light that comes as the default, and had been unable to figure out how to even close the program without resorting to my mouse. Naturally, I was skeptical.

So I launch it, resist the white light as I choose 'evil mode' and watch the window turn dark. The logo appears. It says it's loading. Fetching packages. Eventually, the words "Spacemacs is ready" appear at the bottom of the window. I get it up and running and do some basic programming. I don't know what it is, but something just feels ... good. I spend the rest of the day with Spacemacs, and when the time comes, I don't want to let it go. I go home, dreaming of what I'll do to it. VSCode is nowhere to be found.

That weekend, neither Emacs nor I left my apartment. We were consumed in the fiery throes of passion and configuration, and when the sun rose the following Monday, I knew my heart had been turned.


In truth, it would be another few weeks before I left VSCode completely and migrated to Emacs even at work, but it was coming. Oh, it was coming. To this day, I still stay faithful to Emacs for most of my endeavors, only venturing out when there is something I can't find proper support for (and even then, it's just a matter of time before I take matters into my own hands!).

What I love about it

The extensibility
The fact that you can make it do pretty much whatever you want to is nothing short of amazing. I have never seen an editor this powerful and versatile.
It's the 'self-documenting editor'
Everything in Emacs is documented. You ever wonder what a key does? C-h k <key (or combination)> has got you covered. You wonder what the value of a variable is? C-h v <variable name>. Want help with Emacs? C-h C-h. Boom.
EXWM
The Emacs X Window Manager. Just recently, I finally got it set up to the point where Emacs now manages all of my windows (yes, for all applications) for me. How many other editors can do that? Your move, /'Code/.
Emacs Lisp
I think Lisp is a pretty cool guy. He lets you configure emacs down to the bone. Much like the monarch of beef patties, he let's you 'have it your way', and honestly, that's the way I like it.

What I'm not crazy about

Its single threaded nature
This is usually not much of an issue, unless you somehow end up freezing the main thread, which can happen. In that case, best restart your editor.
Lacklustre support for certain languages
While most languages I use are very well supported in Emacs, I struggle with others. In particular, Microsoft's .NET languages have been hard to get set up properly.
Emacs Lisp
Yeah, it's back. Mostly because I don't really know Lisp yet (relax; it's on my todo list). Until you're comfortable with it, it makes changing your config just that little bit trickier.

What I miss from the VSCode days

So it's not all roses. There are some things I miss from VSCode.

The ease of use
I can't really that the learning curve is steeper; getting started with VSCode is just so easy. Also, installing extensions is a piece of cake.
The massive amount of plugins
Not that Emacs doesn't have packages, but most communities I see seem to have settled on VSCode as their darling, so that's just where you're going to see the most activity and up-to-date plugins in a lot of languages.
The amount of documentation
I mentioned that Emacs is self-documenting. This is great. But it's also ... over 40 years old (1976?! Gee!)! So finding documentation online is tricky in comparison.
The community
Everybody loves Raymond VSCode. That's great and makes it super easy to find articles on how to set it up and configure it and so on. Need help? Ask anyone. Chances are they're using VSCode. The Emacs community is great too, but much, much smaller.

In the end, do I regret it? Not for a second. I think it's a worthwhile tradeoff if you can take the initial learning curve and are willing to put some time into your editor. So if you feel inspired, should you do it? All I can say is that if you pay the upfront cost, Emacs will return it tenfold.