Rarely Used Go Constructs
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!