Comprendre Go en 5 minutes, en Haskell

Voir aussi : vidéo youtube - vidéo peertube - journal linuxfr

D’après leur réputation, Go et Haskell sont des langages de programmation assez opposés : Go privilégie la simplicité, Haskell l’expressivité. Cependant ces deux langages ne sont pas si éloignés que cela. Plus exactement, les concepts de base de Go se retrouvent, de façon assez ressemblante, en Haskell.

Ce tutoriel reprend, en Haskell, les exemples de code en Go de l’article Comprendre Go en 5 minutes. L’objectif n’est pas de présenter exhaustivement Go ou Haskell, ni de démontrer que l’un est “meilleur” que l’autre mais plutôt de montrer l’équivalent en Haskell de quelques fonctionnalités de Go.

Hello world

Commençons par le traditionnel “helloworld”. En Go, il faut définir un package, importer le module fmt et définir une fonction main.

package main

import "fmt"

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

Ou plus simplement :

package main

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

En Haskell, il faut également définir une fonction main mais il n’est pas nécessaire de définir un module ni d’importer de module particulier.

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

Fonctions

En Go, la définition et l’évaluation de fonction ressemble à n’importe quel langage de type “langage C”.

package main

import "fmt"

func add(x int, y int) int {    // définit une fonction add
    return x + y
}

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

En Haskell, on utilise la forme curryfiée et sans parenthèse d’évaluation.

add :: Int -> Int -> Int    -- définit une fonction add
add x y = x + y

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

Go peut utiliser la forme curryfiée également (voir ce commentaire).

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 permet de définir des types et des interfaces. Par exemple, on peut définir un type GoDeveloper implémentant une interface Developer.

package main

import "fmt"

type Developer interface {    // définit une interface Developer
    Code() string
}

type GoDeveloper struct {    // définit un type GoDeveloper
}

func (g GoDeveloper ) Code() string {    // implémente Developer pour GoDeveloper
    return "Go code"
}

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

Le système de type de Haskell est très évolué mais les classes de types et les types algébriques permettent d’écrire un code équivalent au code Go précédent.

class Developer a where    -- définit une "interface" Developer
    code :: a -> String

data GoDeveloper = GoDeveloper    -- définit un type GoDeveloper

instance Developer GoDeveloper where    -- implémente Developer pour GoDeveloper
    code g = "go code"

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

Processus légers

Enfin, Go propose des processus légers, appelés “go routines” et lancés via le mot-clé 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")    // lance un processus léger, en parallèle
    say("hello")
}

Haskell possède également des processus légers, lancés via 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")    -- lance un processus léger, en parallèle
    say "hello"

Erratum. J’avais mal compris le code Go : la fonction say attend juste 100 ms et fait un affichage. Le code donné dans la vidéo n’est pas correct.

Conclusion

Le langage Go permet de définir et d’utiliser des fonctions, des types, des interfaces et des processus légers (appelés “go routines”). Ces fonctionnalités existent également en Haskell et s’utilisent de façon assez ressemblante.