Rarely Used Go Constructs

9 September 2018 go

There are some constructs in Go which are not commonly used. The first can be used a bit like pattern matching, the second is function values of type methods.

“pattern matching”

In some languages like OCaml there is a feature to match data to different patterns:

$ ocaml
# let xor v = match v with 
         (true,true)   -> false
       | (true,false)  -> true
       | (false,true)  -> true
       | (false,false) -> false;;        
val xor : bool * bool -> bool = <fun>
# xor (true,false);;
- : bool = true

In functional languages you can do more advanced things with this, but the functionality to match a tuple of values is handy enough that it would be useful to have. To replicate this behaviour in Go, we can use switch together with a struct which has only compareable fields:

package main

import (
    "fmt"
)

type tuple struct {
    x bool
    y bool
}

func xor(t tuple) bool {
    switch t {
    case tuple{true, true}:
        return false
    case tuple{true, false}:
        return true
    case tuple{false, true}:
        return true
    default: // can only match false, false
        return false
    }
}

func main() {
    a := tuple{true, true}
    b := tuple{false, true}

    fmt.Println(xor(a))
    fmt.Println(xor(b))
}

the really useful part here is the comparison of structs. one could write this also like:

package main

import (
    "fmt"
)

var xor = []fn{
    {tuple{true, true}, false},
    {tuple{true, false}, true},
    {tuple{false, true}, true},
    {tuple{false, false}, false},
}

type tuple struct {
    x bool
    y bool
}

type fn struct {
    in  tuple
    out bool
}

func match(ts []fn, t tuple) bool {
    for _, v := range ts {
        if t == v.in {
            return v.out
        }
    }
    return false
}

func main() {

    a := tuple{true, true}
    b := tuple{false, true}

    fmt.Println(match(xor, a))
    fmt.Println(match(xor, b))
}

here, the xor-functionality is not defined as function xor, but as data slice of fn structs. this may not look spectacular but this definition can also be read from a configuration file now, so functionality can be defined without recomplilation.

type method values

go has first class functions, they can be handed around as values. type methods can also be used as values, without having access to an instance of this type:

package main

import (
    "fmt"
)

type t int

func (i *t) add(x int) {
    *i += t(x)
}

func setup() (fn func(*t, int)) {
    return (*t).add
}

func main() {
    fn := setup()

    for x := t(0); x < 5; fn(&x, 1) {
        fmt.Println(x)
    }
}

the function setup hasn’t access to a specific value of type t, returning a function value to a method of type t. it can later be called with a concrete value as first parameter. related to the first topic of this post, this can be used to setup functionality. an example could be selecting a method of a type which should be used for serving http requests:

package main

import (
    "fmt"
    "io"
    "log"
    "net/http"
)

type HelloBye struct {
    x       int
    handler func(*HelloBye, http.ResponseWriter, *http.Request)
}

func (h *HelloBye) hello(w http.ResponseWriter, r *http.Request) {
    io.WriteString(w, fmt.Sprintf("Hello, my value is %v\n", h.x))
}

func (h *HelloBye) bye(w http.ResponseWriter, r *http.Request) {
    io.WriteString(w, fmt.Sprintf("My value is %v, bye!\n", h.x))
}

func (h *HelloBye) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    h.handler(h, w, r)
}

func setup(name string) (func(*HelloBye, http.ResponseWriter, *http.Request), error) {
    switch name {
    case "hello":
        return (*HelloBye).hello, nil
    case "bye":
        return (*HelloBye).bye, nil
    default:
        return nil, fmt.Errorf("undefined function")
    }
}

func main() {
    f1, err := setup("hello")
    if err != nil {
        log.Fatal(err)
    }

    f2, err := setup("bye")
    if err != nil {
        log.Fatal(err)
    }

    h1 := &HelloBye{1234, f1}
    h2 := &HelloBye{4321, f2}

    http.Handle("/hello", h1)
    http.Handle("/bye", h2)
    log.Fatal(http.ListenAndServe(":8080", nil))
}

in action:

$ curl localhost:8080/hello localhost:8080/bye
Hello, my value is 1234
My value is 4321, bye!