
Golang Diaries II - espadrine
https://www.tbray.org/ongoing/When/201x/2013/07/15/Golang-Diaries-2
======
espadrine
Of course, anyone can make a Map function for any particular type combination,
but Tim Bray has a point. The generic, functional map function cannot be
written, and never will.

There is an issue of elegance. If generics were to be added, lists and maps
would need to have similar syntax, because they feel and behave like generics.
However, they cannot. Their syntax is… not generic enough.

Oddly enough, elegance through consistency was never reached when designing
built-in functions like make(): those can be applied on any type, but user-
defined functions don't have this freedom. Essentially, there is a Java-like
divide between built-in types (which, for instance, you cannot create methods
on) and user types. Their rules of elegance don't apply to the built-ins,
which is quite odd.

I doubt that this impedes on programming efficiency compared to C. However,
some of the duplicated code out there cannot be factored into a library, and
map is an example of that.

~~~
pdeuchler
I took a couple minutes to whip up a quick map function, to be honest I didn't
quite believe you. This may be my naivete, but is there any way it's not
sufficiently generic for types? I realize it's not suitable for maps, but it
satisfies the OP's request for a map that can convert arrays of type Foo to
type Bar

[http://play.golang.org/p/jxMFq5UYs1](http://play.golang.org/p/jxMFq5UYs1)

~~~
drewolson
I think the point is, if you write map like this you might as well be using a
dynamically typed language in the first place because you're deriving no
benefit from the type checker. In fact, you're just using casting to
completely circumvent it.

~~~
pdeuchler
Of course. If the main cornerstone of your project is map it's probably not
the greatest idea to use Go, but if you're using Go for other reasons and
happen to need a quick map function there's no reason to change languages just
because Go doesn't have a standard library implementation

~~~
bad_user
But then it's pretty odd to use Go because you like the language, while not
being able to design a proper map().

And map() btw is only the tip of the iceberg in functional programming. But
then again, Go is not a functional programming language.

------
comex
> Isn’t it pathetic that so many allegedly-mainstream languages don’t have
> HTTP libraries that are this natural and idiomatic?

That's after 13 SLoC to make a single HTTP request - I like Go, but that seems
almost like a parody. It's not that different from other languages, and the
error handling makes it look longer, but for something described as "natural
and idiomatic" I would expect it to be one function call plus one failure
test, not six and three...

~~~
throwit1979
When people complain about comprehensive error-checking, it makes my blood
boil.

Checking and handling all possible error conditions is a GOOD thing.

~~~
azth
> Checking and handling all possible error conditions is a GOOD thing.

I agree. But Go seems to have taken the most verbose way to achieve that.
Pattern matching should have been the way to go if they did not want to use
exceptions. Then again, Go does not have functional constructs, and you
wouldn't be able to get far with it. Though it still prevents you from
ignoring errors, unlike what exists in the language.

------
ExpiredLink

         if ... { return failure }
    

three(!) times in a row and then he writes:

> _Isn’t it pathetic that so many allegedly-mainstream languages don’t have
> HTTP libraries that are this natural and idiomatic?_

Isn’t it pathetic that a wannabe mainstream language doesn’t provide exception
handling?

~~~
jameskilton
Exceptions are horrible if you're trying to make a fast, dependable, reliable,
low-level, concurrent modern day programming language, as Go is. Exceptions
are also horrible in that they never improve the readability and
understandability of any code they exist in. The Go team, some of whom
actually designed C back in the day, are well familiar with Exceptions, why
they are bad, and explicitly chose to not implement them.

So no, using the word "pathetic" implies a gross oversight. This was a very
deliberate decision, and a good one at that.

------
dscrd
Just recently, I needed a concurrent map for my project. I looked for a
library and started using it. Fast forward a bit and I realized the map was
completely untyped. Fast forward a slightly longer bit and I realized (perhaps
wrongly? Enlighten me) that there was no way to implement a concurrent map
library that wasn't untyped.

We need generics, or perhaps it's more goy to have the same features by some
more clever way. But still. We need those features and bad.

I'll be looking at Rust with some more interest meanwhile.

------
jlujan
Screw map, let me pass a comparator function into sort for interfaces.

~~~
cmccabe
That's not necessary. You can just create a type that embeds the type you're
sorting, and define the Less function however you like. It's very efficient
since you're just wrapping the entire slice object, not every element in the
slice.

    
    
      package main
      import "sort"
      type RevIntSlice struct {
          sort.IntSlice
      }
      func (arr RevIntSlice) Less(i, j int) bool {
          return arr.IntSlice.Less(j, i)
      }
      func main() {
        myList := RevIntSlice { sort.IntSlice {1, 2 , 3 } }
        sort.Sort(myList)
        for _, elem := range(myList.IntSlice) {
            println(elem)
        }
      }
    

Note that if all you want to do is reverse the sort order, sort.Reverse is a
better way than what I wrote here. This is just an example of the kinds of
things you can do.

~~~
chongli
>That's not necessary. You can just create a type that embeds the type you're
sorting

But that's more work (and more boilerplate) than simply passing an anonymous
comparator to the sort function.

~~~
cmccabe
Let me guess. You are coming from Java. In Java this would be:

    
    
      myArray.sort(new Comparator<MyClass> ({
        @Override
        CompareTo(MyClass lhs, MyClass rhs) {
          return rhs.compare(lhs);
        }
      }));
    

Is that really shorter? I don't think so.

In any case, there is a more elegant solution in Go, which is simply:

    
    
      sort.Sort(sort.Reverse(array))
    

You need to open your mind a little bit, to learn a new language.

~~~
chongli
_You are coming from Java._

Nope. Clojure and Haskell. Clojure:

    
    
        user=> (sort > (vals {:foo 5, :bar 2, :baz 10}))
        (10 5 2)
    

Haskell:

    
    
        >>> sortBy (flip compare) . map snd $ [("foo", 5), ("bar", 2), ("baz", 10)]
        [10,5,2]

------
illumen
tbray works for Google as "Developer Advocate". Which means he markets to
developers.

This is a marketing fluff piece from Google.

"I still haven't written 1000 lines of Go." Why do we care what this guy says
then? Because lots of Googlers vote it up.

I suggest you flag this spam :)

------
buro9
> Isn’t it pathetic that so many allegedly-mainstream languages don’t have
> HTTP libraries that are this natural and idiomatic?

But at every point where Tim then returns a failure we lose insight as to why
something failed.

My preferred idiom for handling all errors when it comes to web requests or
responses is to return both the error (as it has the detailed message of what
happened) and the http status code that matches the error (as it has
translated the message into a code that describes the type of error).

This bit would change:

    
    
        if !strings.HasPrefix(mediaType, "application/json") {
            // bogus media type, deal
            return failure
        }
    

to:

    
    
        if !strings.HasPrefix(mediaType, "application/json") {
            // bogus media type, deal
            return HttpErr{Err: errors.New(""), Status: http.StatusUnsupportedMediaType}
        }
    

Whatever receives that knows there has been an error, and has the code
available to do tell the developer or end user something meaningful without
relying on the error message.

I'm sure others are also creating their own http error as well, so whilst Go
does http really well it seems in practise that there are still a few places
where it could benefit from a little extra to help standardise the code we
write.

All of those Http codes are here: [http://golang.org/pkg/net/http/#pkg-
constants](http://golang.org/pkg/net/http/#pkg-constants)

Regarding the map stuff, the only other place the lack of generics has stood
out has been within slices of a type of thing.

The scenario is: I have a slice of identifiers in an order important to me,
for each of these concurrently fetch the record being identified and create a
slice of those records ordered as per the initial slice.

We've ended up using this type of thing:

    
    
        // Envelope for making sorted concurrent requests for Comment
        type CommentRequest struct {
            Comment Comment
            Err     error
            Seq     int
        }
    
        type CommentRequestBySeq []CommentRequest
    
        func (v CommentRequestBySeq) Len() int           { return len(v) }
        func (v CommentRequestBySeq) Swap(i, j int)      { v[i], v[j] = v[j], v[i] }
        func (v CommentRequestBySeq) Less(i, j int) bool { return v[i].Seq < v[j].Seq }
    
        // Much later in the code...
    
        // Make a request for each id
        req := make(chan CommentRequest)
        defer close(req)
    
        for seq, id := range ids {
            go HandleCommentRequest(id, seq, req)
        }
    
        resps := []CommentRequest{}
        for i := 0; i < len(ids); i++ {
            resp := <-req
            if resp.Err != nil {
                return resp.Err
            }
            resps = append(resps, resp)
        }
    
        // Sort them
        sort.Sort(CommentRequestBySeq(resps))
    
        // Extract the values
        ems := []Comment{}
        for _, resp := range resps {
            ems = append(ems, resp.Item)
        }
    

In that example a Comment is a comment made on a forum web page and we're
fetching lots concurrently, but needing to return them in the order that they
will be displayed.

The problem stems from that envelope, and the need to implement that sort
function for every type that we ever want to concurrently fetch and sort.

It's the only place where I think, if we just had generics this could be a lot
better.

~~~
jalfresi

      if !strings.HasPrefix(mediaType, "application/json") {
            // bogus media type, deal
            return HttpErr{Err: errors.New(""), Status:    http.StatusUnsupportedMediaType}
        }
    

Sorry, but having dealt with a HTTP library in PHP that did something similar,
I would have to hunt you down and kill you. The HTTP response status code from
the original request was NOT 415 Unsupported Media Type. Do you have any idea
how misleading this error is to the developer? The error you SHOULD be
returning is an Application error code "Unexpected Media Type", NOT a HTTP
error code. It's the application that has a problem with the returned media
type.

~~~
buro9
> I would have to hunt you down and kill you

Attempts at off-the-cuff witty death threats don't tend to work too well
online. I truly hope you were jesting.

~~~
jalfresi
Apologies, yes it was an attempt at humor. I was hoping for more of a
discussion on the two approaches though if your still game.

~~~
buro9
Sure.

My scenario is a web service, that calls other web services.

It throws an error and then has to return something meaningful to the callee,
which is usually both a message that tells the developer calling the web
service how to fix it (if it's something that they can fix) as well as the
relevant response code.

The advantage from assigning the code at the earliest point the error is
detected is that one avoids any later text parsing of the error message to
figure out the right code to return. It also allows general switching on the
error code range to see whether you really need to throw the error or whether
there is some other branch of logic that could be done instead.

------
cmccabe
With regard to the functional-style "map" function, is it really that much
less typing than a simple for loop?

    
    
      output := make(output_type, len(input))
      for idx, e := range(input) { output[idx] = convert(e) }
    

compared to:

    
    
      output := map(input, func(in input_type) output_type {
          return convert(in)
        }
      )
    

There are cases where functional programming can be a lot terser (for better
and worse), but I don't think this is one of them. My guess is that the
original poster is just adjusting to different ways of doing things between
Golang and Ruby-- that's natural.

My biggest complaint about Go so far is that you have to use an external
library to get an ordered tree structure. Maps suffice most of the time, but
sometimes you need ordering.

[edit: fixed map example]

~~~
jacques_chester
Loops impose, and guarantee, ordered traversal of a collection. Maps do not.
This means that maps can sped up by dividing up the collection into smaller
parts and mapping each of them in parallel.

For a language that has concurrency as a central design motivation, it's an
unfortunate omission.

As we say in the database world: think in sets, not in arrays.

~~~
icebraining
It's an imperative language with side-effects, unordered mapping would break
expectations and produce buggy code.

You can still achieve those speed ups, you just need to be explicit about the
division (e.g., by creating a channel, launching goroutines and then
collecting the results).

[https://sites.google.com/site/gopatterns/concurrency/paralle...](https://sites.google.com/site/gopatterns/concurrency/parallel-
for-loop)

~~~
jacques_chester
I'm not talking about what Go _is_ ; I'm more interested in what it might have
been. There's nothing in the law that prevents mapping with side effects. If
the side-effects don't require a particular order, why constrain them?

~~~
icebraining
_I 'm not talking about what Go is; I'm more interested in what it might have
been._

Well yes, but there are certain core concepts that _define_ Go, and being
imperative with side-effects is one of them. If we drop that, then we're no
longer discussing Go but some hypothetical language.

 _There 's nothing in the law that prevents mapping with side effects. If the
side-effects don't require a particular order, why constrain them?_

But they aren't constrained, they just aren't built-in. Go has loops, not
maps, but if one wants to make a mapping function, the link I posted shows
there's no constraint in having it execute out of order.

~~~
jacques_chester
I think we're just restating our arguments. I'm happy to leave it here.

