A Project in Haskell: One Week Apps
Lately we've focused on some of the finer points of how to learn Haskell. But at a certain point we want to actually build things. This next series of articles will focus on some of the more practical aspects of Haskell. This week, I'm going to introduce one of my own Haskell projects. We’ll first examine some of the reasons I chose Haskell. Then we’ll look at how I’ve organized the project and some of the pros and cons of those choices.
Introduction to One Week Apps
The program I've created is called One Week Apps (Names have never been my strong suit...I'm open to better ideas). It is designed to help you rapidly create mobile prototypes. It allows you to specify important elements of your app in a simple domain specific language. As of current writing, the supported app features are:
- Colors
- Fonts
- Alert popups
- Programmer errors (think NSError)
- Localized strings
- View layouts
- Simple model objects.
As an example, suppose you wanted to create an app from scratch and add a simple login screen. You’ll start by using the owa new
command to generate the XCode project itself. First you'll enter some information about the app though a command line prompt. Then it will generate the project as well as some directories for you to place your code.
>> owa new
Creating new OWA project!
What is the name of your app:
>> Blog Example
What is the prefix of your app (3 letters):
>> BEX
What is the author's name:
>> James
What is your company name (optional):
>> Monday Morning Haskell
Your new app "Blog Example" has been created!
We then start specifying the different elements of our app. To represent colors, we'll put the following code in a .colors
file in the generated app
directory.
Color labelColor
Hex 0x000000
Color fieldColor
Hex 0xAAAAAA
Color buttonColor
Blue 255
Red 0
Green 0
We can also specify some different fonts for the elements on your screen:
Font labelFont
FontFamily HelveticaNeue
Size 12.0
Styles Light
Font fieldFont
FontFamily Helvetica
Size 16
Font buttonFont
FontFamily Arial
Size 24
Styles Bold
Now we'll add some localization to the strings:
NAME_TITLE = “Name”
“NAME_PLACEHOLDER” = “Enter Your Name”
PASSWORD_TITLE = “Password”
“PASSWORD_PLACEHOLDER” = “Enter Your Password”
SUBMIT_BUTTON_TITLE = “Log In!”
Finally, we'll specify the view layout itself. We can use the colors and fonts we wrote above. We can also modify the layout of the different elements.
View loginView
Type BEXLoginView
Elements
Label nameLabel
Text “NAME_TITLE”
TextColor labelColor
Font labelFont
Layout
AlignTop 40
AlignLeft 40
Height 30
Width 100
TextField nameField
PlaceholderText “NAME_PLACEHOLDER”
PlaceholderTextColor fieldColor
PlaceholderFont fieldFont
Layout
Below nameLabel 20
AlignLeft nameLabel
Height 30
Width 300
Label passwordLabel
Text “PASSWORD_TITLE”
TextColor labelColor
Font labelFont
Layout
Below nameField 40
AlignLeft nameLabel
Height 30
Width 100
TextField passwordField
PlaceholderText “PASSWORD_PLACEHOLDER”
PlaceholderTextColor fieldColor
PlaceholderFont fieldFont
Layout
Below passwordLabel 40
AlignLeft nameLabel
Height 30
Width 300
Button submitButton
Text “SUBMIT_BUTTON_TITLE”
TextColor buttonColor
Font buttonFont
Layout
Below passwordField 40
CenterX
Height 45
Width 200
Then we'll run the owa gen
command to generate the files. We need to add them manually to your XCode project (for now), but they should at least appear in the proper folder. Finally, we'll add a few simple lines of connecting code in the view controller:
class ViewController: UIViewController {
override func loadView() {
view = BEXLoginView()
}
...
And we can take a look at our basic view:
Rationale For Haskell
When I first came up with the idea for this project, I knew Haskell was a good choice. One major feature we should notice is the simple structure of the program. It has limited IO boundaries. We read in a bunch of files at the start, and then write to a bunch of files at the very end. In between, all we have is the complicated business logic. We parse file contents and turn them into algebraic data structures. Then we perform more transformations on those depending on the chosen language (you can currently use Objective C or Swift).
There is little in the way of global state to track (at least now when there's no "compiling" occurring). There are no database connections whatsoever. Many of the things that can make Haskell clunky to deal with (IO stuff and global state) aren’t present in the main body of the program.
On the contrary, the body consists of computations playing to Haskell’s strengths. These include string processing, representing data abstractly, an so on. These were the main criteria for choosing Haskell for this project. Of course, there are libraries to deal with all the “clunky” issues I mentioned earlier. But we don’t even need to learn those for this project.
Architecture
Now I’ll give a basic overview of how I architected this program. I decided to go with a multi-package approach. We can see the different packages here in my stack.yaml
file:
packages:
- './owa-core'
- './owa-model'
- './owa-utils'
- './owa-parse'
- './owa-objc'
- './owa-swift'
- The
model
section contains the datatypes for the objects of the mobile app. Other packages rely on this. - The
parse
package contains all code and tests related to parsing files. - The
objc
package contains all code creating Objective C files and printing them out. It also has tests for these features. - The
swift
package does the same for Swift code. - The
core
package handles functionality like the CLI, interpreting the commands, searching for files, and so on. - The
utils
package contains certain extra code needed by multiple packages.
Pro and Cons
Let’s look at some of the advantages and disadvantages of this architecture. As an alternative, we could have used a single-package structure. One advantage of the chosen approach is shorter compile time within the development cycle. There is a tradeoff with total compile time. Having many packages lead to more linking between libraries, which takes a while. However, once you've compiled the project once, each successive re-compile should take much less time. You’ll only be re-compiling the module you happen to be working on in most cases. This leads to a faster development cycle. In this case, the development cycle is more important than the overall compile time. If the program needed to be compiled from scratch on a deployment machine with some regularity, we might make a different choice.
The organization of the code is another important factor. It is now very obvious where you’ll want to add parsing code. If a feature seems broken, you know where to go to find a failing test or add in a new test to reproduce the bug. This system works far better than the haphazard version-based test organization I had earlier.
Another advantage I would list is it’s now a cleaner process to add another language to support. To support a new language, there will be few changes necessary to the core module. You’ll add another package (say owa-android
) and add a few more options to Core. This should make it an easy repository to fork for, say, anyone who wanted to make an android version.
Let’s also consider some of the disadvantages. It is unlikely many of these packages will be used in total isolation from one another. The owa-parse
module firmly depends on the owa-model
package, for instance. The language specific modules will not interact with each other. But they're also not particularly useful unless you're using the parser anyways.
Additionally, the utils
module is a real eyesore. It has a few random utilities needed by the testing code for several packages as well as the printing code. It seems to suggest there should only be one package for testing, but I find this to be unsatisfactory. It suggests instead there should be common printing code. It may be a reasonable solution to simply leave this code duplicated in certain places. This is another code-smell. But if different languages evolve separately, then their utility code might also.
Summary
So what did we learn about Haskell from this project? Haskell is great at internal processing. But programs with wide IO bounds can cause some headaches. Luckily, One Week Apps has lots of internal processing, but limited IO bounds. So Haskell was a natural choice. Meanwhile, multi-package organization has some advantages in terms of code organization. But it can also lead to some headaches as far as placing common code.
One Week Apps is now open source, so feel free to check it out on Github! Do you love the idea and think it would supercharge your app-writing? You should contact me and check out our issues page!
Want to contribute but have never touched Haskell? You're in luck, because we've got a couple great resources for getting started! First, you should check out our Getting Started Checklist. It'll walk you through some of the basic steps for installing Haskell. It'll also show you some awesome resources to kickstart your Haskell code writing.
If you have a little experience but want more practice, you should download our Recursion Workbook. It'll teach you about recursion, a fundamental functional paradigm. It also has some practice problems to get you learning!