Understand Go in 5 minutes, in Haskell

See also: youtube video (french) - peertube video (french) - version française

Go and Haskell are rather opposite: Go favors simplicity, Haskell favors expressiveness. However, these two programming languages are not so different. More precisely, the basic concepts in Go are also available in Haskell, and quite similarly.

This post translates, in Haskell, the code samples presented in this article. The objective is not to detail Go and Haskell, nor to demonstrate that one is better than the other, but only to illustrate equivalent Haskell code to some Go code.

Hello world

Let’s start with a classic “helloworld”. In Go, we have to define a package, import the fmt module and define a main function.

package main

import "fmt"

func main() {
    fmt.Println("hello world")
}

Or more simply:

package main

func main() {
    println("hello world")
}

In Haskell, we also have to define a main function, but it’s not mandatory to define a module.

main :: IO ()
main = putStrLn "hello world"

Functions

Go has functions. The syntax for defining or evaluating a function is quite similar to any C-like language.

package main

import "fmt"

func add(x int, y int) int {    // define a function add
    return x + y
}

func main() {
    var i, j int = 10, 2
    fmt.Println(add(i, j))    // evaluate add
}

In Haskell, functions are defined using the curried form. Function evaluation needs no parenthesis.

add :: Int -> Int -> Int    -- define a function add
add x y = x + y

main :: IO ()
main = do
    let i = 10
        j = 2
    print (add i j)    -- evaluate add

Go can use the curried form too (see this comment).

package main

func main() {
    var add = func(a int) func(b int) int {
        return func(b int) int {
            return a + b
        }
    }

    var x = 10
    var y = 2
    println(add(x)(y))
}

Interfaces

Go can define types and interfaces. For example, we can define a GoDeveloper type that implements a Developer interface.

package main

import "fmt"

type Developer interface {    // define an interface Developer
    Code() string
}

type GoDeveloper struct {    // define a type GoDeveloper
}

func (g GoDeveloper ) Code() string {    // instantiate Developer for GoDeveloper
    return "Go code"
}

func main() {
    goDeveloper := GoDeveloper{}
    fmt.Println(goDeveloper.Code())
}

The Haskell type system is very powerful but the previous code can be implemented quite simply, using type classes and algebraic data types.

class Developer a where    -- define an "interface" Developer
    code :: a -> String

data GoDeveloper = GoDeveloper    -- define a type GoDeveloper

instance Developer GoDeveloper where    -- instantiate Developer for GoDeveloper
    code g = "go code"

main :: IO ()
main = do
    let goDeveloper = GoDeveloper
    putStrLn (code goDeveloper)

Lightweight processes

Finally, Go has lightweight processes, called “go-routines”. A go-routine is basically a function called asynchronously using the keyword go.

package main

import (
    "fmt"
    "time"
)

func say(s string) {
    for i := 0; i < 5; i++ {
        time.Sleep(100 * time.Millisecond)
        fmt.Println(s)
    }
}

func main() {
    go say("world")    // launch a lightweight process asynchronously
    say("hello")
}

Haskell also has lightweight processes, launched using forkIO.

import Control.Concurrent
import Control.Monad
import System.Clock

say :: String -> IO ()
say s = forM_ [1 .. 5] $ \_ -> do
    threadDelay 100000
    putStrLn s

main :: IO ()
main = do
    forkIO (say "world")    -- launch a lightweight process asynchronously
    say "hello"

Conclusion

The Go language can define and use functions, types, interfaces and lightweight processes ("go-routines). These features also exist in Haskell and can be used quite similarly.