Haskell Polyglot on Pi: Day 1
I’ve been intrigued by tiny computational devices since the early 80’s. We didn’t have embedded systems-on-a-chip, such as the Raspberry Pi, back then, but were able to accomplish a lot with very underwhelming hardware. Still, you had to be adept at assembly language and basic electronics to build anything.
It’s not just the computational power of today’s tiny devices that has spurred the recent explosion of the Internet of Things. Many of today’s embedded applications don’t require tons of computational power. (At least not yet!) The IoT industry is rocketing into relevance because of a combination of factors: the ubiquity and maturity of the internet, the recent growth of the communities around embedded systems, and the plummeting costs of the hardware.
So, after three decades of waiting and watching, I’m ready to build something, but what language should I use? C and Python are the two most popular languages for Raspberry Pi development. Most people just choose one of these – possibly needing to learn it or refresh their skills – and start coding, but I’m not swayed so easily. I know that the language will greatly affect my product’s ability to grow and mature over time.
So, I guess step #1 is to choose a language. Or perhaps I’ll “go polyglot” and choose more than one language since no single language excels in every category.
Why not start with my favorite language? Haskell.
As you’ll soon discover, I quickly encountered some gotchas, but determined that it’s doable. I’m optimistic that the situation will improve because there are many people interested in using Haskell for Raspberry Pi development.
Why bother with other languages?
So why not just use C or Python? Both are extremely popular, general-purpose languages. Most of the examples and utilities for Raspberry Pi has been written in these languages. Programmers likely chose C because it’s known for creating ultra-fast executables. Whereas they probably chose Python because it’s approachable and easy to understand.
But which of these allows you to eliminate most run-time exceptions? Launch dozens – or even hundreds – of ultralight threads? Manage concurrency effortlessly? Scale your codebase with minimal discipline? Neither. (If you believe otherwise, I’ll gladly debate, but I’m guessing you probably haven’t delved deeply into other languages that excel in these areas.)
So why not use Go or Rust or Nim? These languages have gained momentum on embedded systems. I hate coding in Go. (Despite this, it’s on my “favorites list”, but that’s a story for another blog post!) Rust and Nim look like most other C-like languages with some nice bonus features. I plan to try these two languages soon, especially if I think I might need their special features.
But none of these languages has quite the same set of features as Haskell.
While no language is perfect, Haskell is good at some of the things that most languages aren’t: elimination of exceptions through advanced type safety, super scalable code patterns, ultralight threads, and extremely simple concurrency. The first two are the most important and most unique. When I write code in Haskell, I feel confident that it will work correctly and that it will continue to work correctly even if the codebase goes through dozens of iterations.
On the other hand, Haskell is less approachable, is less adept at conserving memory, and suffers from a less evolved ecosystem than more popular languages.
For these reasons, I often view Haskell as the central core of a polyglot solution. I’ve worked on and/or proposed solutions based on Haskell + Go, Haskell + Python, and Haskell + node.js. It’s just another way to separate concerns: build the highly computational, algorithmic parts (i.e. the core product) in Haskell and build the simpler parts (e.g. REST APIs, web services, etc.) in the other language.
So this is how I’d like to approach my projects on the Raspberry Pi, too.
First Project: Playing with GPIO
To me, the most exciting thing about the Raspberry Pi is the GPIO, General Purpose Input / Output. It’s pretty exciting to interact with the real world. GPIO makes it super simple to do both analog and digital I/O.
Seems like a good place to start playing.
To Compile or Cross-compile?
Haskell is a compiled language, so my first question is: where do I run the compiler? Since my main development machine is a Mac running Darwin (Unix) on an Intel i7 and my Raspberry Pi runs Ubuntu (Linux) on an ARMv7 processor, I can’t compile a single executable that can run on both the Mac and the Pi. To run an app on the Pi, I must either compile an executable on it directly, which would be very slow, or I must create a cross-compiler for the Mac, which would take some effort, but would compile the executable lightning fast.
The leading Haskell compiler is GHC, the Glasgow Haskell Compiler. It’s fairly simple to build a cross-compiling GHC because it uses the well-documented and robust GCC under the hood. Just configure GHC to use a cross-compiling GCC and GHC will output cross-compiled code. Bingo!
Unfortunately, there’s one significant caveat: a cross-compiled GHC fails when it encounters Template Haskell. Since Template Haskell is one of the most widely used language extensions of GHC, this is pretty much a show-stopper for all but the most trivial of projects. Doh.
Pending further research, cross-compilation is not an option at this point. Looks like compile time is going to also be coffee break time. 😦
I encounter the next problem almost immediately. The leading Haskell project management tool, Stack, does not yet work on ARM cpu architectures. Stack isn’t critically necessary for Haskell development, but it’s really nice to have. You can achieve many of the same benefits using Cabal, the dependency management tool that Stack uses internally, and LTS Haskell releases. If you only care about one Haskell project at a time on your Raspberry Pi, you probably don’t even need Cabal sandboxes, but if you’re writing the code in your favorite editor or IDE on your main development machine, you’ll likely want to use sandboxes.
I know of two possible workarounds for the lack of Stack on the Pi: (1) run Stack on the codebase from my Mac and only run GHC on the Raspberry Pi, or (2) use Cabal sandboxes and LTS Haskell in both environments. I think option (1) is the one I’d eventually like to use, but I’ve been having trouble discovering how Stack runs GHC. Ideally, it would tell me all the command line options to feed into GHC, so I can duplicate them on the Raspberry Pi. So far, I can’t seem to get it to do that. Stack doesn’t seem to be very transparent.
So for now, I’m just using plain old Cabal and LTS Haskell.
Since I’m writing the code from my Mac, I need both machines to have access to the codebase. One of the easiest ways to do this is `sshfs`. First, I `ssh` into the Pi:
ssh rpi3.local -l john
Then, I set up the share:
sshfs email@example.com:/Users/john/Code/rpi ~/Projects/rpi/
Of course, your user names and directory structures will be different.
Searching Hackage reveals two gpio libraries, HPi, and hpio. Of these, hpio looks like it is better documented and supported, so I try it first. HPi promises to be faster since it does some memory-mapping tricks, so it might be worth a look later.
hpio’s primary API is a monad transformer stack. Why does every Haskell developer feel that they need to write a monad these days? Thankfully, the lower-level APIs are also exposed so I can bypass the monad transformer stack if it proves to be unwieldy.
I decide to try the simplest code example from hpio. The example just lists all of the available gpio ports on the machine. I copy the code and compile it on my Mac. No problems. When I run it on the Mac, though, it spits out a message that there are no gpio ports on my machine. Of course, this is the boring result that I expected. I then compile the app from the Pi and run it, and…
I get permissions errors. Bummer.
After about 30 minutes of researching and typing every combination of `sudo` and `chmod` – and several thoughts about trying the other gpio library – I finally get that ah ha! moment. The compiled executable is on my Mac’s file system, not the Pi’s!
I move the executable to a convenient location on the Pi and run it. It lists all of the gpio ports! Success!
Next, I copy and paste the code from the second example, compile it, and run it on the Pi. It appears to work, too, but I can’t be sure until I connect some electronics.
I connect an LED to gpio port 14, and verify that it was truly working when the LED blinks on and off.
Here’s a picture of the complete hardware and electronics. It’s pretty basic at this point, of course. It’s a Raspberry Pi B+ running Ubuntu 16.04, an Adafruit T-Cobbler Plus cable, a small bread board, some wire leads, and an LED with current-limiting resistor (330 ohm).
Now it’s time to write my own app. Stay tuned for a future blog post.
About the Author
The Haskell Platform – Haskell with batteries included.
raspberrypi.org – The official community for Raspberry Pi.