6 ms·
For fear of disagree downvotes: I would say that many of the qualms brought up in this article are problems that are encountered fighting the language. The pro
by tshadwell 12y ago
For fear of disagree downvotes: I would say that many of the qualms brought up in this article are problems that are encountered fighting the language.
The problem of 'summing any kind of list' is not a problem that is solved in Go via the proposed kind of parametric polymorphism. Instead, one might define a type, `type Adder Interface{Add(Adder)Adder}`, and then a function to add anything you want is fairly trivial, `func Sum(a ...Adder) Adder`, put anything you want in it, then assert the type of what comes out.
When it comes to iteration, there is the generator pattern, in which a channel is returned, and then the thread 'drinks' the channel until it is dry, for example `func (m myType) Walk() chan->myType` can be iterated over via `range v := mt.Walk(){ [...] }`. Non-channel based patterns also exist, tokenisers usually have a Next() which can be used to step into the next token, etc.
The Nil pointer is not unsafe as far as I know, from the FAQ: http://golang.org/doc/faq#no_pointer_arithmetic http://golang.org/doc/faq#no_pointer_arithmetic
The writer seems to believe that functions on nil pointers crash the program, this is not the case. It's a common pattern in lazy construction to check if the receiving pointer is nil before continuing.
Go is not flawless by any means, but it warrants a specific style of simplistic but powerful programming that I personally enjoy.
- wyager 12y ago>The writer seems to believe that functions on nil pointers crash the program, this is not the case. It's a common pattern in lazy construction to check if the receiving pointer is nil before continuing. And what happens when you don't check? It crashes. That's the unsafe part. These crashes are simply not possible in Rust and Haskell, and the type system notifies you if failure is possible (because the function will return an Option/Maybe).
- deleted 12y ago[deleted]
- Dewie 12y agoMaybe "unsafe" is being used to mean different things, here. Some may interpret it as to refer to unsafe memory access. Others may use it to mean possibility of crashing at run time (due to null pointer dereference).
- TheHydroImpulse 12y agoNull pointer dereference is unsafe memory access.
- Dewie 12y agoNot if you just throw something like an exception when the program tries to do it instead of actually accessing that memory location.
- richo 12y agothat's what mapping an -rwx page at 0x0 does, and as a result it segfaults, which is an access violation.
- dbaupp 12y agoThere is some subtlety about dereferencing a null pointer. Many languages (C, C++, Rust) state that *NULL is undefined behaviour, that is, the compiler can assume that it never happens and optimises based on this. This can lead to a "misoptimised" program that doesn't actually segfault when the source suggests it should. http://blog.llvm.org/2011/05/what-every-c-programmer-should-know.html http://blog.llvm.org/2011/05/what-every-c-programmer-should-...
- jjnoakes 12y agoNo, you are wrong. First, mapping that page doesn't cause all null pointer dereferences to segfault. And second, the language doesn't require a segfault. In fact, it explicitly permits the implementation to do whatever it likes. That is the difference between safe and unsafe. It is in the language definition.
- dbaupp 12y ago"the language"... which one? AIUI, Go doesn't state that null dereferences are undefined behaviour, but rather that they are guaranteed to panic.
- shadowmint 12y agoWoa, Rust is great, but I think you're going a little far on the hyperbole there. You can easily generate a segfault in Rust in 'unsafe' (or 'trusted') code; that might only restrict errors of that nature to code that uses unsafe blocks. Practically speaking that's pretty common; once you enter an FFI unsafe block, you lose all type safety; but you can totally do it without FFI too. Eg. using transmute(). In fact, there's no way to know if you code contains 'hidden' unsafe blocks wrapped in a safe api in some 3rd party library that might cause a mysterious segfault later on. You can argue that 'if you break the type system you can do anything, obviously'; that's totally true. I'm just pointing out the statement: "These crashes are simply not possible in Rust and Haskell" <-- Is categorically false. You can chop your own arms off in Rust just like anything else (including Go).
- dbaupp 12y ago> You can easily generate a segfault in Rust in 'unsafe' (or 'trusted') code; that might only restrict errors of that nature to code that uses unsafe blocks, but practically speaking that's pretty common; once you enter an FFI unsafe block, you lose all type safety; but you can totally do it without FFI too. Eg. using transmute(). Not directly addressing what you're saying, but, IME people are far too quick to use `unsafe` code. One needs to be quite careful about it as there's a pile of invariants that need to be upheld: http://doc.rust-lang.org/master/rust.html#behavior-considered-unsafe http://doc.rust-lang.org/master/rust.html#behavior-considere... > once you enter an FFI unsafe block, you lose all type safety You don't lose all type safety, especially not if the FFI bindings you're using are written idiomatically (using the mut/const raw pointers correctly; wrapper structs for each C type, rather than just using *c_void, etc).
- rakoo 12y ago> It's a common pattern in lazy construction to check if the receiving pointer is nil before continuing. I disagree: if the construction can fail, the constructor must return an error, which will be checked; only if the error is nil can the process continue. There shouldn't be logic on the actual data returned to assert whether a constructor worked or not.
- tshadwell 12y agoI meant something like this: http://play.golang.org/p/eqnDLVMHGA http://play.golang.org/p/eqnDLVMHGA (pseudocode)
- frowaway001 12y ago> put anything you want in it, then assert the type of what comes out ... which is exactly what the article mentions and criticizes?
- coldtea 12y ago>For fear of disagree downvotes: I would say that many of the qualms brought up in this article are problems that are encountered fighting the language. If that's so, it's because it's a language that also fights lots of things a modern programmers wants to do/have.
- ithkuil 12y ago>When it comes to iteration, there is the generator pattern, in which a channel is returned, and then the thread 'drinks' the channel until it is dry, for example `func (m myType) Walk() chan->myType` can be iterated over via `range v := mt.Walk(){ [...] }`. Non-channel based patterns also exist, tokenisers usually have a Next() which can be used to step into the next token, etc. Actually using channels as a general iterator just for the sake of using the range operator is considered as an anti-pattern. The reason is not performance (although it has a cost), but the risk of leaking producer goroutines. Your example: for v := range mt.Walk() { if blah { break } } How will the goroutine writing into the channel returned by mt.Walk know when there are no more consumers which will possibly read from it? One way out is: done := make(chan struct{}) for v := range mt.Walk(done) { if blah { break } } close(done) // or defer close(done) Picking the right cleanup is error-prone. What about errors? How will mt.Walk tell you that it had to interrupt the iteration because an error happened? Either your channel has a struct field containing your error and your actual value (unfortunately Go lacks tuples or multivalue channels). Furthermore uncaught panics in the producer goroutine will generate a deadlock, which will be caught by the runtime, but it will halt your process. One way to do it is: errChan := make(chan error) for v := range mt.Walk(errChan) { if blah { break } } err := <-errChan The producer will use the select statement to write both to errChan and your result channel. The success of writing to errChan is a signal for the producer that the consumer exited. However same thing here about relying on the last statement being executed to avoid a leak in case of returns or panics. Here the defer is less nice since you're supposed to do something with the error: func Example() (err error) { errChan := make(chan error) for v := range mt.Walk(errChan) { if blah { break } } defer func() { err = <-errChan }() } Next-style methods just pass through the panics, and allow you to handle errors either by having a func Next() (error, value) or with this pattern which moves the pesky error handling outside: i := NewIterator() for i.Next() { item := i.Item() ... } err := i.Error() First, any panic that happens inside either your code or the generator will bubble through. Second, if you return from your loop body, you will have to provide your own error (the compiler will remind you about your function signature, if in doubt). You can return early if the iterator can be stopped and GCed out (i.e. it doesn't handle goroutines or external resources), otherwise you'd have to call a cleanup as with channels. The rule of thumb with Go should be that you don't have to do things just because they use some syntactic sugar. After a while you start to think about beauty in terms of properties not about calligraphy. However, I do see this as a weak point of the language, which hopefully can be solved by education; after all Go is so simple to learn that you might be tempted to make it look even simpler. But the fact that the language has (almost) no magic, it means that you can actually understand what some code does, which imho outweighs the occasional syntactical heaviness or having to learn a few patterns.