You're using IE. Scroll down.
home :: tech :: whatswrongwithgo

What’s wrong with Go anyway ?

Who do you think you are knocking on Go and Ken this way ?

First, I don’t care for that tone, are we in church ? Second, all languages have good and bad stuff. Javascript has a lot of bad parts and it doesn’t stop being my favorite language.

On to the knocking

package main

import "fmt"

func main() {
    fmt.Println("Hello, world")
}

Nothing much here except that Println that throws me directly to Java. Also, there’s a strange lack of ;; which is disconcerting but might not be a problem in the end.

func fib() func() int {
    a, b := 0, 1
    return func() int {
        a, b = b, a+b
        return a
    }
}

func main() {
    f := fib()
    // Function calls are evaluated left-to-right.
    fmt.Println(f(), f(), f(), f(), f())
}

There’s a disturbing number of func right in the first line. Obviously Go is strongly typed and I’m immediately sitting in Introduction to C trying to figure out to prototype a function that returns a list of functions that return void * and take two functions as arguments each taking two pointers to int as arguments having the function an argument that’s an array for pointers to void. This isn’t a good feeling. On the plus side Go’s syntax seems more natural reading left to right as “a function fib that takes no arguments and returns a function that returns an int”. Then on the next line we have something I always hated with a passion, :=. I understand the point of making = and == very distinct but if you want that change == not =. := is just non natural except for a very strict group of computer scientists.

Then we have a return statement with a repetition from the fib() proto. Some people will say this is just good programing making sure you got the right thing, me I was saying just a couple of days ago every time you have to write exactly the same thing in two places you’re doing something very wrong. Of course you’re going to return func() int, you’ve said it 2 lines ago. And then there’s something I put right up there with i += i++ + ++i. What’s a,b = b,a+b supposed to mean ? Do you do a = b and then b = a+b ? Is it a list assignment of (b,a+b) to (a,b) ? I can’t tell. I’m sure somehow Go has got ()less lists and it’s a list assignment but it confuses the hell out of. Also, = ? There’s two assignment operators ? One for initialization and another for changing values ? Really ? At this point we got :=, = and ==.

One point for Go, this example shows closure. On the other hand everything is strongly typed except variables which are inferred. f := fib() means, f is a variable of type whatever fib() returns so if fib’s interface ever changes some interesting stuff will happen and the interpreter will never be the wiser. You get all the hassle of typing and none of the benefits.

The next example is a bit longer so I’ll go inline.

// Number is a pointer to a Number
type Number *Number

Really ? Number is a pointer to Number ? Are you on crack ?

// The arithmetic value of a Number is the
// count of the nodes comprising the list.
// (See the count function below.)

// -------------------------------------
// Peano primitives

func zero() *Number {
    return nil
}

func isZero(x *Number) bool {
    return x == nil
}

func add1(x *Number) *Number {
    e := new(Number)
    *e = x
    return e
}

Let’s try to digest this in the context of Number is a pointer to itself. You create a new Number called e. First, new(Number) makes me sick after all these years of trying to kill new in JS. Then you say *e = x which I have no idea what’s supposed to do. Looks like pointer manipulation and you’re throwing away the new(Number) and pointing e to x. Or you could be deferring x somehow and making the value of e be the same as x.

func sub1(x *Number) *Number {
    return *x
}

No clue what this is supposed to do. You’re taking an argument and returning.

func add(x, y *Number) *Number {
    if isZero(y) {
        return x
    }
    return add(add1(x), sub1(y))
}

Still lost, this looks like it’s splicing stuff off y but using sub1 that looks like an identity function. The * in *x and *Number must be different I guess.

func mul(x, y *Number) *Number {
    if isZero(x) || isZero(y) {
        return zero()
    }
    return add(mul(x, sub1(y)), x)
}

func fact(n *Number) *Number {
    if isZero(n) {
        return add1(zero())
    }
    return mul(fact(sub1(n)), n)
}

Still lost.

// -------------------------------------
// Helpers to generate/count Peano integers

func gen(n int) *Number {
    if n > 0 {
        return add1(gen(n - 1))
    }
    return zero()
}

Lets try and work out gen. You tail from nil then go add1(nil) which is *e = nil. I sense * = is some kind of append list operation. Now if *e was pop wouldn’t that be funny ?

func count(x *Number) int {
    if isZero(x) {
        return 0
    }
    return count(sub1(x)) + 1
}

Well, looks like it is. You’re splicing stuff off x and counting how many times you can do it before you run out of elements. This also tells me arguments are passed by value so this Number arrays are being copied left and right.

// -------------------------------------
// Print i! for i in [0,9]

func main() {
    for i := 0; i <= 9; i++ {
        f := count(fact(gen(i)))
        fmt.Println(i, "! =", f)
    }
}

After all the weirdness above main is pretty simple. As far as I can tell Number is a list of Number objects and * can either push or pop depending on being a lvalue or a rvalue. Hopefully the next one is better.

import (
    "fmt"
    "math"
)

So this should be a list of strings. Which means a,b = c,d isn’t list assignment. Hum.

func main() {
    fmt.Println(pi(5000))
}

// pi launches n goroutines to compute an
// approximation of pi.
func pi(n int) float64 {
    ch := make(chan float64)
    for k := 0; k <= n; k++ {
        go term(ch, float64(k))
    }
    f := 0.0
    for k := 0; k <= n; k++ {
        f += <-ch
    }
    return f
}

make. Is it like new ? But different ? (turns out new allocates and returns a pointer like in C++ but make allocates some specific types and doesn’t return a pointer - bad) chan seems to be a qualifier so ch is a chan of floats. Then you can run a loop going a function on ch. Maybe this is the parallelization feature I’ve heard about. This is probably the first neat thing on the language so far so lets see where this goes. I’m sure term is defined to take a float64 but you need to type cast the int to keep the interpreter happy and you do it in a awkward way too, is float64() a function ? A keyword ? Syntax for cast ? Is it even important ?

A couple of lines down we find <-ch which looks like an iterator or generator. The syntax is awkward versus <= but the principle seems sound, you fill ch with values by going term on it and then pull them out as needed. The syntax does allows lazy evaluation but go func(ch) is kinda strange.

func term(ch chan float64, k float64) {
    ch <- 4 * math.Pow(-1, k) / (2*k + 1)
}

So <- is 2 operands, left is pop and right is push apparently, makes some sense. Not sure I like it though. Hopefully this is a back pressure mechanism and you evaluate term to fill ch when it reaches a low watermark. The syntax seems hard to follow and what if a single generator function fill more than one channel ? Confusing.

// Send the sequence 2, 3, 4, ... to channel 'ch'.
func Generate(ch chan<- int) {
    for i := 2; ; i++ {
        ch <- i // Send 'i' to channel 'ch'.
    }
}

Oh groan an actual generator with more specific prototype syntax. So what’s term ?

// Copy the values from channel 'in' to channel 'out',
// removing those divisible by 'prime'.
func Filter(in <-chan int, out chan<- int, prime int) {
    for {
        i := <-in // Receive value from 'in'.
        if i%prime != 0 {
            out <- i // Send 'i' to 'out'.
        }
    }
}

Ok, a pipe filter. There’s a lot of work here to make streams fit into “normal” for loop syntax. I find filter functions much more intuitive.

// The prime sieve: Daisy-chain Filter processes.
func main() {
    ch := make(chan int) // Create a new channel.
    go Generate(ch)      // Launch Generate goroutine.
    for i := 0; i < 10; i++ {
        prime := <-ch
        fmt.Println(prime)
        ch1 := make(chan int)
        go Filter(ch, ch1, prime)
        ch = ch1
    }
}

First time I read this main it felt like i was reading Conway’s Quantum Superpositional Perl. Turns out this is just a bad intro example that does something really fancy. ch is your 2.. channel and you can take a prime number from it right of the bat cause we know 2 is prime. Then you create a new channel that filters on this prime make ch this channel. So by now ch emits 2,3,5,7,9,.. . Then you extract the 3 and add a new filter to the channel filters using 3 so now you have 2,3,5,7,11,.. . And so on. Despite the awkward syntax this is actually very cool.

So what’s wrong with Go anyway ?

The syntax is very very awkward. It makes it hard to read both at the statement level and at the package level. It has a lot of brilliant stuff like streams and concurrency but the syntax is like if a C programmer read about Perl, wanted to make a language as awesome as Perl but looking like Python and not totally alien to Java programmers. As a result we get some stuff that’s proven a mistake in Javascript and madness like type T struct { a int } func (tv T) Mv(a int) int { return 0 } // value receiver func (tp *T) Mp(f float32) float32 { return 1 } // pointer receiver var t T

t.Mv(7)
T.Mv(t, 7)
f := T.Mv; f(t,7)

which is a declaration of an object with 2 methods crammed into C. The 3 function/method calls are equivalent.

Looking at the examples and some of the documentation it seems Go must undergo the same process JS is/should be undergoing, remove a bunch of optional syntax and confusing shorthands that allow easily misreadable idioms. When that happens Go will kick ass!

/tech | edited on 2013/01/06 -- permalink, click to comment
blog comments powered by Disqus
Archive: