I enjoy learning new programming languages. It isn't always feasible to dive deeply into a language, but there is always a lot to gain from picking up the basics. An appreciation of how different languages solve different problems provides a lot of useful context around the trade-offs of various engineering decisions.

Finding a decent tutorial is only the first step. A good tutorial typically gives a good idea of how to use the compiler/interpreter and some syntax basics but precious little else. The real gem is the eponymous Hello World program that seems to appear in every description of any programming language.

I don't usually start with Hello World anymore. While it is useful for learning how to structure a simple program and writing to standard output, it doesn't do anything of value. For this reason, the first programs I write in a new language are a few basic Unix-like shell utilities. I typically start with true, false, and echo before looking at something more interesting.

True is the simplest program you can write. It does nothing but terminate immediately upon start-up, signalling success to the operating system. False is the same, but signals failure instead.

-- true.hs - do nothing successfully (Haskell)

module Main (main) where

import System

main = exitWith ExitSuccess

Echo is little more than an extension to Hello World or the typical greeting program, having to read the command line arguments and write them back to standard output.

# echo.py - print command line arguments

import sys
import getopt

newline = '\n'
space = ' '
omitnewline = False

def main():
  global omitnewline
  options, args = getopt.getopt(sys.argv[1:], 'n')

  for opt in options:
    if '-n' in opt:
      omitnewline = True
  if not omitnewline:

if __name__ == "__main__":

With these basic programs written, we have a starting point for doing something more interesting. Cat is a good good choice at this stage; it reads from standard input or from a list of files and outputs the text directly to standard output without any modifications. This gives us an opportunity to play with the file abstraction and IO. It might also be worth looking at simple command line argument parsing at this stage.

// cat.go - concatenate files

package main

import (

func cat(fd *os.File) {
    const NBUF = 512
    var buf [NBUF]byte
    for {
        switch nr, er := fd.Read(buf[:]); true {
            case nr < 0:
                    "cat: error reading from %s: %s\n",
                    fd, er.String())
            case nr == 0: // EOF
            case nr > 0:
                if nw, ew := os.Stdout.Write(buf[0:nr]); nw != nr {
                        "cat: error writing from %s: %s\n",
                        fd, ew.String())

// concatenate the specified files, joining with a newline.
// If -n is specified, the newline will be omitted.
func main() {
    if flag.NArg() == 0 {
    for i := 0; i < flag.NArg(); i++ {
        fd, err := os.Open(flag.Arg(i))
        if fd == nil {
            fmt.Fprintf(os.Stderr, "cat: can't open %s: error %s\n",
                flag.Arg(i), err)
        defer fd.Close()

From here, there are any number of different programs you could write. You could continue to focus on shell utilities or start branching out into other areas of interest. All this is really just a starting point for playing with the language and it's associated tools. By now you should be able to write simplistic filters and know everything you need to get started on something larger. None of these tools are big enough to warrant modular or object oriented design, so a simple but larger program may be a worthy challenge. What kind of problems you want to play with from here is up to you.