Simplicity, the 2nd

Newsflash: There’s a project on Github now that allows you to use LINQ-style queries in Go! Whoa. Let me catch my breath.

Don’t get me wrong. I think this is a very interesting package, and nicely written, too. I applaud all the effort that has gone, and is still going, into this. The one thing that bothers me is how verbose the example code looks:

import . "github.com/ahmetalpbalkan/go-linq"
...
type Student struct {
    id, age int
    name string
}
...
over18Names, err := From(students)
    .Where(func (s T) (bool,error){
        return s.(*Student).age >= 18, nil
    })
    .Select(func (s T) (T,error){
        return s.(*Student).name, nil
    }).Results()

Look at that. We need to specify type information just to create a few lambdas, but the result is of type T — which is just an empty interface, AKA void*. This is because there are still no generics in Go, which was one of my pet peeves with Java for a long time (and let’s be frank here, the way generics are implemented in Java by now is just brain-dead). And with a half-assed type system, there can be no type inference, either. And this is my biggest problem with Go. One day, maybe the example might resemble something like this:

import . "github.com/ahmetalpbalkan/go-linq"
...
type Student struct {
    id, age int
    name string
}
...
over18Names, err := From(students)
    .Where(s => s.age >= 18, nil)
    .Select(s => s.name, nil)
    .Results()

Well, I cheated big time here, introducing not only a simplified lambda syntax, but generics, better type inference and a monad along the way, effectively transforming Go into C# or Vala. But one day, with enough modifications to the compiler, idiomatic Go code might look like this.

Go’s big contender, D, already supports generics and a nice lambda syntax:

import stuff;
...
struct Student {
    int id;
    int age;
    string name;
}
...
auto over18Names = map((Student s) => s.name,
                       filter((Student s) => s.age >= 18, students));

This brings us back to the topic of simplicity. D is not exactly known for being simple. While not as complicated as C++, it still contains lots of stuff to learn before you can use it idiomatically. Go’s creators, on the other hand, go to great lengths to keep it simple, making sacrifices to the language’s power here and there.

Discuss: Has the quest for simplicity gone the wrong way here, or is it just a trade-off like any other?

Python, Erlang, Haskell and a few others include List Comprehensions directly into the language, all three looking more or less like this:

over18Names = [i.name for i in students if i.age >= 18]

Can’t get shorter or more readable than that. The problem here is that the syntax is fixed, you can’t just introduce anything new on your own. That’s still the domain of languages like Lisp, Tcl, and Forth, the triumvirate I wrote about last time. Oh, and Python is not exactly a very performant language. Nothing wrong with that — most tasks can be done quickly enough, and readability does matter. Still miss my macros though, and the GIL is a bitch.

Now let’s compare the previous examples to the Lisp family I keep raving about. Common Lisp has had the (loop) macro for ages:

(defstruct Student id age name)

(setq over-18-names
  (loop for i in students
        if (>= (student-age i) 18)
        collect (student-name i)))

Succinct, readable, and damn fast. SBCL still beats both Go compilers hands down when it comes to performance, and compares quite favorably to DMD. If you want a “real” functional example, no problem either.

Alright, so Common Lisp is the big elephant in the room and not exactly simple. Let’s have a look at my darling, Scheme:

(load "structs.scm")
...
(defstruct Student (id age name))
...
(define over-18-names
  (map student-name
       (filter (lambda (s) (>= (student-age s) 18))
               students)))

Compile this with any Scheme compiler worth a dime, do a micro-benchmark (yeah, I know), and you’ll throw Go out of the window without a second thought. Here is a language that is extremely simple to implement (though quite hard to get 100% right if you want to stick to every single part of the standard), learn, and extend. The compilers are mature and the code produced exhibits an astonishing performance, even when compared to what are currently considered state-of-the-art VMs like the .NET CLR, the JVM or LLVM. Goroutines, you say? Have a look at Gambit-C/Termite. If that’s not enough for you, Go won’t help you either. In that case, Erlang is right over there. HIPE is a cool thing ™.

And this is where I bang into a wall again. I honestly can’t understand why Scheme and Erlang aren’t used more widely. Can’t be dynamic typing — look at Perl and Python. Can’t be the GC — even Go as a systems programming language does it. Can’t be their age — C is doing just fine. Is it because they look a little different? Are parentheses (Scheme) and Prolog-esque Syntax (Erlang) really THAT bad? I’m a fish out of water here…

Advertisements

2 thoughts on “Simplicity, the 2nd

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s