Golang Diaries II
tbray.org
tbray.org
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.
[1] - https://github.com/BurntSushi/ty#examples [2] - https://github.com/BurntSushi/ty/blob/master/fun/list.go#L17
And map() btw is only the tip of the iceberg in functional programming. But then again, Go is not a functional programming language.
https://github.com/droundy/gotgo/blob/master/example/slice.g...
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...
If you feel like golang needs one method to do all the above, feel free to write one and post it to the mailing list... I doubt that it does, but feel free.
Of course there are other cases where the difference matters. It just... doesn't seem very "natural and idiomatic" to me, that's all.
Checking and handling all possible error conditions is a GOOD thing.
As for single-threaded map, there are at least two necessary behaviors that handle errors very differently:
1. batch all errors/exceptions/etc and return them as an array 2. return on first error
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.
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?
So no, using the word "pathetic" implies a gross oversight. This was a very deliberate decision, and a good one at that.
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.
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.But that's more work (and more boilerplate) than simply passing an anonymous comparator to the sort function.
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.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]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 :)
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
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.
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.Attempts at off-the-cuff witty death threats don't tend to work too well online. I truly hope you were jesting.
Always code as if the guy who ends up maintaining
your code will be a violent psychopath who knows
where you live.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.
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]
output := map(convert, input)
After all, map generally handles fetching the element from the array.That's only true in Go because Go doesn't have type inference for closures. In ML for example this would be:
let output = map (fn i -> convert i) input
or, more succinctly: let output = map convert inputLanguages make a whole set of related design choices and typically none of them makes sense without considering the others. (Sometimes none of them makes sense AFTER considering the others, but that's another story.)
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.
SQL is great. Not all languages have to be SQL.
The modern looping construct imposes order not by design, but by historical accident. Working with a collection of data in assembler works by proceeding through memory addresses in linear order and then jumping back to the top. That pattern got lifted into higher level languages via the DO loop in Fortran.
It's an example of path dependency. Why do we assume that loops are the only natural way to deal with collections? Because that's just how the path of history unfolded. I think the die was cast when it turned out to be easier to build Turing machines than lambda calculators.
Edit: I don't know who downvoted you, but I hope it was an accident.
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...
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.