Yet another Go thread



  • A while ago, I mentioned I'd ported a hobby project from CoffeeScript to Go. Since this is now the DailyWhatTheGo forums, I've been meaning to post my own experience. Here you go, in no particular order of importance: (I've used C-like pseudocode to illustrate the concepts except where the syntax is the point of the WTF)

    • Exceptions aren't thrown in the traditional sense. They're returned. (Technically you _can_ throw them, but this is a bad practice and, for example, you won't write a library that threw exceptions because your caller won't expect it to.) So a function that wants to call 3 functions in serial would look like

      
      result1, err = func1()
      if (err != null)
      	return err
      

      result2, err = func2(result1)
      if (err != null)
      return err

      result3, err = func3(result2)
      if (err != null)
      return err

      and so on. There are no C macros like Windows has for HRESULT's, so no way to avoid writing this.

    • Go is supposed to force you to check for exceptions (in the same spirit as Java's checked exceptions). Functions can have multiple return values and it is convention to return a tuple of (result, error) where one and only one is non-null. There is also a syntax for destructured assignment.

      
      result, err = someFunctionThatCouldFail()
      if (err == null)
      	return err; // Bubble up exception
      // else result is valid. Use it...
      

      Writing

      
      result = someFunctionThatCouldFail()
      

      is a compiler error. So in this way, the programmer is kinda forced to not ignore exceptions. Good.

      However, if someFunctionThatCouldFail doesn't actually have a return value but can still throw an exception, then you'd call it as

      
      err = someFunctionThatCouldFail()
      

      Go allows you to ignore the return value in this case, which means the lazy programmer could just as well write

      
      someFunctionThatCouldFail()
      someOtherFunction()
      

      and this would call someOtherFunction() regardless if someFunctionThatCouldFail() succeeded or not.
      And here I thought the days of On Error Resume Next were behind us. This language has it on by default!

    • Continuing with exceptions, C# exceptions have a string message and a stack trace. Java exceptions have a string message and a stack trace. Go exceptions have a string message. No stack trace. Just the equivalent of a ToString(). So even if you do bubble your "someFunctionThatCouldFail() failed!" exception all the way to your topmost catch-all error handler/logger, it's going to have no idea where the exception actually came from. You could of course put some form of source line information manually in the message, but that still doesn't give you a stack trace of where it was called from. Good luck debugging!

    • Go comes with a few libraries to help you get a basic HTTP server started. You set up a server and register handlers on certain URLs; pretty standard stuff. One of the available handlers is a static files handler. But it's totally untweakable:

      • It has a hard-coded mapping of file extension to Content-Type.
      • You can't tell it to not serve certain file types.
      • You can't control the output. It renders a flat series of <a> tags without any surrounding <html> or <body> or any other HTML.
    • When sending a response from an HTTP server, in most other libraries, you either have the freedom to set the status code and the headers in any order, or you have to set the status code first and the headers second. In Go's http module, you have to set the headers first and the code second.

    • Another touted benefit of Go: You can compile binaries for all platforms from any platform! Except on Windows, it wants gcc to compile for Linux. Oops!

    • Go has pointers. Go has interfaces. A type T can implement interfaces. A pointer to T, T*, ... can implement interfaces. Any interfaces it wants. Totally unrelated to what are implemented by T. In fact, this is how the Exception interface works, it's implemented by pointers, not by actual types. That is, if you have a custom exception type MyException, then it's not MyException that implements the Exception class's ToString(), it's MyException*

    • Related to the above, Go sometimes allows you to write foo.bar where you actually should write (*foo).bar Only sometimes, though. So if you want to be sure, you'd probably write it as the latter all the time just to be consistent.

    • Want to initialize a variable? foo := bar

      Want to assign a variable a new value? foo = bar

      The compiler knows perfectly well when you assign a value to an uninitialized variable, so this annoying difference is totally cosmetic and pointless.

      (Yes, I know it's significant if you want to make a new variable to shadow one in an outer scope. Personally I'd rather that shadowing wasn't allowed at all. I've never come across any instance in all my software dev life where shadowing was _necessary_.)

    • On the subject of classes, there aren't any. There are structs. Remember how in C, you don't define member functions on a struct? Instead you define them outside and usually put the struct type as the first parameter? That's how it's done in Go too.

    • Similarly, Go doesn't have constuctors. The convention to have a constructor for a type Foo is to make a static NewFoo method. Of course, this doesn't prevent the caller from ignoring it and calling new Foo() themselves, so when looking at API docs, always remember to check if a NewFoo method exists or not first! And if you're writing a class, better throw any guarantee that your object will be in a consistent state out of the window!

    • Function overloading isn't allowed either. I sure hope you only need one set of parameters for your constructor!

    • No generics either.

    • There's a printf (format string, varargs). There's a println (single string). There's no println that takes a format string and varags, so you have to write "\n" in all of your println's, since 99% of the usages of println are for writing lines.

    • Just like the C++ STL, there's no rational thought put behind when something should be a member function and when something should be a static function on the type. So methods like string.Contains are actually static methods on the strings type.

    • Speaking of strings, how do you append a character to a string? First you have to call a method to convert the character to a string. Then you call a method to append the new string to the original string.

    • Go is supposed to enforce strict whitespacing via compiler errors, and some of them indeed are. Good. (This isn't about the gofmt tool.) But a lot of them aren't. Bad. It's very inconsistent on what it does think is an error and what isn't.

    • Windows is pretty much a third-class citizen right now. For example, a function that takes in a path string and returns the path after resolving all symlinks returns the input path unmodified on Windows. Looking at the source commits, there is a commit that claims to have fixed it but actually didn't. Of course, one of the touted benefits of Go is that you can compile binaries for all platforms from any platform! I'll just compile a binary for Linux and test there. Oh wait. On Windows, it wants a gcc to be able to compile for Linux. Oops!

    Now Go does have a lot of nice features. It compiles very fast, and the channels are very nice. Unfortunately the language is full of warts that totally make it unworkable, and the attitude of the zealots on the mailing list (basically the only place apart from the API docs where you can get any answers) makes it difficult for me to bother. I may be wrong on some of these because I only spent a week or so on this before giving up, so I'll be happy to be corrected.



  • @Arnavion said:

    • Exceptions aren't thrown in the traditional sense. They're returned. (Technically you _can_ throw them, but this is a bad practice and, for example, you won't write a library that threw exceptions because your caller won't expect it to.) So a function that wants to call 3 functions in serial would look like

      
      result1, err = func1()
      if (err != null)
      	return err
      

      result2, err = func2(result1)
      if (err != null)
      return err

      result3, err = func3(result2)
      if (err != null)
      return err

      and so on. There are no C macros like Windows has for HRESULT's, so no way to avoid writing this.

    So basically it's like the C method of returning -1 from a function to indicate an error (except when it returns false for error and true for success, or NULL for error, or ... or ...) - only with the improvement that it returns the actual result and the error status separately, rather than using in-band signalling like C does.  Sounds like a small incremental improvement to me.

    I also don't really see any difference between having to write "if (err != null)" vs. "if (HR_FAILED(err))" apart from the second taking three more keypresses.

    @Arnavion said:

    • When sending a response from an HTTP server, in most other libraries, you either have the freedom to set the status code and the headers in any order, or you have to set the status code first and the headers second. In Go's http module, you have to set the headers first and the code second.

    Just like PHP or perl-cgi then?

    @Arnavion said:


    • Another touted benefit of Go: You can compile binaries for all platforms from any platform!
      Except on Windows, it wants gcc to compile for Linux. Oops!

    @Arnavion said:
    • [ . . . ] Of course, one of the touted benefits of Go is that you can compile binaries for all platforms from any platform! I'll just compile a binary for Linux and test there.
      Oh wait. On Windows, it wants a gcc to be able to compile for Linux. Oops!

    Obviously you think that one's important, since you mentioned it twice, but I'm not sure what the WTF is.  How else is it supposed to cross-compile for Linux targets?  Were you expecting it to use MSVC?




  • @DaveK said:

    @Arnavion said:

    • Exceptions aren't thrown in the traditional sense. They're returned. (Technically you can throw them, but this is a bad practice and, for example, you won't write a library that threw exceptions because your caller won't expect it to.) So a function that wants to call 3 functions in serial would look like

      
      result1, err = func1()
      if (err != null)
      	return err
      

      result2, err = func2(result1)
      if (err != null)
      return err

      result3, err = func3(result2)
      if (err != null)
      return err

      and so on. There are no C macros like Windows has for HRESULT's, so no way to avoid writing this.

    So basically it's like the C method of returning -1 from a function to indicate an error (except when it returns false for error and true for success, or NULL for error, or ... or ...) - only with the improvement that it returns the actual result and the error status separately, rather than using in-band signalling like C does.  Sounds like a small incremental improvement to me.

    I also don't really see any difference between having to write "if (err != null)" vs. "if (HR_FAILED(err))" apart from the second taking three more keypresses.

    Here, let me rewrite your horrible code in valid Go syntax:

    result1, err := func1()
    if err != nil {
    	return nil, err
    }
    
    result2, err := func2(result1)
    if err != nil {
    	return nil, err
    }
    
    return func3(result2)


  • @DaveK said:

    How else is it supposed to cross-compile for Linux targets? Were you expecting it to use MSVC?

    Why not? A compiler's a compiler.



  • @Ben L. said:

    Here, let me rewrite your horrible code in valid Go syntax:

    Yep, rewriting ugly pseudocode into Go produces ugly Go code.
    Did you have a point here somewhere?



  • @DaveK said:

    So basically it's like the C method of returning -1 from a function to indicate an error (except when it returns false for error and true for success, or NULL for error, or ... or ...) - only with the improvement that it returns the actual result and the error status separately, rather than using in-band signalling like C does.  Sounds like a small incremental improvement to me.

    Sorry if I wasn't clear. I'm not complaining about the fact that it has C-style returning exceptions instead of throwing them. So yes, I agree it's an improvement over C. The problem is that there's no in-built bubbling of exceptions up; the programmer has to be mindful to check for the returned error himself which is more verbose and ironically itself error-prone (as I showed in point 2).

    @DaveK said:

    I also don't really see any difference between having to write "if (err != null)" vs. "if (HR_FAILED(err))" apart from the second taking three more keypresses.

    I was talking about things like

    
    #define MUST_SUCCEED(operation) hr = operation; if (FAILED(hr)) { return hr; }
    

    ...

    HRESULT hr;

    MUST_SUCCEED(callToAFunctionThatReturnsAnHResult1())
    MUST_SUCCEED(callToAFunctionThatReturnsAnHResult2())
    MUST_SUCCEED(callToAFunctionThatReturnsAnHResult3())


    But yes, I was wrong that it was part of the Windows headers. You'd have to define it yourself.

    @DaveK said:

    Obviously you think that one's important, since you mentioned it twice, but I'm not sure what the WTF is.  How else is it supposed to cross-compile for Linux targets?  Were you expecting it to use MSVC?

    Whoops, I was shuffling them around to make contextual sense and somehow duplicated that.

    What's the point of advertising "Go can cross-compile to any target!" if it needs an actual separate cross-compiler to do that for me? I could just do it manually then. I know it's unrealistic to expect them to have bundled a real Linux compiler + headers + libraries but given this odd advertising, that was exactly what I thought they'd done.

    Besides, somehow, I don't think anyone would want to run a Linux binary compiled using gcc on Windows on their servers. I certainly wouldn't.


  • Discourse touched me in a no-no place

    @blakeyrat said:

    @DaveK said:
    How else is it supposed to cross-compile for Linux targets? Were you expecting it to use MSVC?
    Why not? A compiler's a compiler.
    That really depends on what the intermediate format is, and whether that intermediate format is accessible in a way that is usable with other front-end processors. If there's no interface in MSVC to plug into (other than spitting out C or C++ as an intermediate, which is a relatively common approach with some languages, but is pretty nasty and rarely produces optimal code) or the interfaces that are there are not compatible with the ones the original main compiler developers were using, the effort of porting to use MSVC as a back-end is substantial. Without a large team of programmers, that sort of thing tends to fall by the wayside; after all, the actual dependency works for real on Windows. After all, a compiler's just a piece of software.

    No WTF here.

    The attitude of Go to exceptions though, especially given that they can actually make some guarantees about the quality of the implementation of them (unlike the morass of problems that traditionally plagued C++), that would be an excellent thing to beat up on them about. And I don't want to know what was being smoked by the person who thought that allowing types on a pointer itself was a good idea. In terms of language design, those are the ones that make me go “WTF?!”



  • @DaveK said:

    Just like PHP or perl-cgi then?

    Is there a technical reason for implementing it this way? Because it seems backwards to me.


  • Discourse touched me in a no-no place

    @Arnavion said:

    Is there a technical reason for implementing it this way? Because it seems backwards to me.
    It's probably related to the exact point at which they write the headers.



  • @dkf said:

    That really depends on what the intermediate format is, and whether that intermediate format is accessible in a way that is usable with other front-end processors. If there's no interface in MSVC to plug into (other than spitting out C or C++ as an intermediate, which is a relatively common approach with some languages, but is pretty nasty and rarely produces optimal code) or the interfaces that are there are not compatible with the ones the original main compiler developers were using, the effort of porting to use MSVC as a back-end is substantial. Without a large team of programmers, that sort of thing tends to fall by the wayside; after all, the actual dependency works for real on Windows. After all, a compiler's just a piece of software.

    Translation: Go developers are lazy. Gotcha.

    But that's still a WTF. Hell you could sum up dozens of WTFs as "the developers were lazy".



  • @Ben L. said:

    Here, let me rewrite your horrible code in valid Go syntax:
    Whoosh
    (that was the sound of ben missing the fucking point!)



  • @Arnavion said:

    A while ago, I mentioned I'd ported a hobby project from CoffeeScript to Go.
    Well there's your problem, you wanted to use Go. Go is a joke, a badly executed and unfunny joke, but a joke; why does anyone take it seriously? They are optimizing all the wrong things and missing the point. Its developed under the delusion that anything I write will be awesome because I am awesome and always right, and know more than everyone else, because everything i think of is perfect and correct even if though i put zero effort into learning anything known on the topic because all those phd's and scientists that came before me don't know shit!



    If you need a scripting language use python, everyone seems to love it, all kinds of jobs require it. Go is a joke, python has a jokey name; go is shit, python is crack for programmers (incidently LINQ is heroin!)





  • @Ben L. said:

    QDDwwePbDtw

    Yes, as I said in my first post, I like the channels and goroutines features. It's the other language "features" that spoil it.


  • Considered Harmful

    My current thought is that Go is not a bad language, it's an experimental language. The thinking seems to have been, "let's throw all these ideas at a dartboard and see which ones stick." A third of them were obviously terribad, a third them sounded promising but didn't really pan out, and maybe a few of them were genuinely good ideas that are worth keeping (I'm still waiting to hear what they are).

    Go is the application of the old adage, "build one to throw away, you will, anyway."



  • Hello there. I've been reading TDWTF for a while now, but I didn't bother to register on the forums up until now. I'd like to answer to most, if not all, of the issues the OP raised here about the go language. The things I want to point out are factual errors, not opinions or subjective things. I will try to be the least offensive as possible, and I apologize beforehand if I fail at this.


    Exceptions aren't thrown in the traditional sense. They're returned. (Technically you can throw them, but this is a bad practice and, for example, you won't write a library that threw exceptions because your caller won't expect it to.)

    There's a difference between errors (aka error messages) and exceptions. When an exception happens (i.e. is raised/thrown/etc), the call stack unwinds, and takes down your program, violently (unless you catch the exception). But you already know this, most probably.

    Now, in contrast, an error is not something that happens, it doesn't do anything on its own. An error is most of the time a string, and it holds a message about the error.

    I'm pointing this out because you're referring to go error types as "exceptions". They are not exceptions. They are a piece of memory with type and value and whatnot. They're variables that can be printed or otherwise reported to the user (or the caller code). That's why they're called error messages, or errors for short.

    So, I ask you to refer to errors as errors, and to exceptions as exceptions. And as you have pointed out, go has an exception-like mechanism (but they're used differently), but those are called panics, so those shouldn't be called exceptions either.

    About verbosity. Yes, checking errors manually does seem more verbose than relying on exception handling; where you just ignore the the fact that any line of code can potentially throw an exception, and let the caller handle it. If you haven't got used to it yet, then writing all those ifs and returns is a bit tiresome, but they're also readable. Obviously you won't see the difference when writing short scripts, where handling errors is always the same (return until suitable level on call stack, then print it), but when you actually have to write code that handles errors in different ways, then:

    • you are not forced to handle the error in an error-handling-only codepath (in contrast, try and catch branches enforce exactly this), meaning you can even have multiple errors in flight at the same time (i.e. store them in different variables), and be able to do other stuff before you handle or return the errors. In contrast, when you catch an exception, you have to handle it there and then (or re-throw it).

    • you don't have to play the "what state gets invalidated when an exception is thrown here" game, because you explicitly see what happens in case of error. Handling returned errors is explicit, while letting exceptions fall-through is implicit. When you're debugging and trying to figure out what code caused your data to be invalid, and you're looking at the source, you don't explicitly see the exception that had caused some code not to run, or had caused some code not to be undone. Especially if you've caught that exception in an upper level than where the data was invalidated.

    • In concurrent code, it's much harder to use exceptions safely and properly, because if you use it in case of any kind of invalid behaviour, then you have to be very careful that every thread (or goroutine, in go's case) is guarded so that none of them can tear down the whole program, only if explicitly necessary. It's just plain simpler to use error values, with which I can easily control which thread gets torn down and which stays. And even if you write convential go code and don't raise panics, only for truly unrecoverable faults, even then it's pretty tricky to ensure that every thread gets torn down (and by extension, cleaned up), in the correct order, and to ensure that the main thread exits last. Then again, I haven't had the time to figure this out properly, nor have I asked for help on the mailing list (I've run into this issue fairly recently).

    There are several arguments for and against both returning errors and exception handling, but I'm not going to discuss all of them here. Some additional food for thought, though:
    http://blogs.msdn.com/b/oldnewthing/archive/2004/04/22/11816-aspx
    http://blogs.msdn.com/b/oldnewthing/archive/2005/01/14/352949.aspx


    Go is supposed to force you to check for exceptions (in the same spirit as Java's checked exceptions). Functions can have multiple return values and it is convention to return a tuple of (result, error) where one and only one is non-null. There is also a syntax for destructured assignment.

    No, go is not supposed to force you to check for returned errors. What if you have good reasons to ignore a returned error? For example, when I call fmt.Println for debugging purposes, I don't generally care inside the program that it can't access the standard output (which would be very weird, and I as a user of my own computer would have noticed it), in part because it's very unlikely to happen. And so what if it can't access the stdout? It's not that it can write an error message to stdout that it can't access stdout.

    For a less silly example, what if I'm processing a big file line-by-line, and one line has weird data in it that I don't care about, because all the other lines are still valid? I can't stop for one measely line that has a misplaced semicolon in it. So I ignore the error. As long as the processing function recognises the invalid data and doesn't corrupt the program state, it's cool. And what if I do report the error? Well, what if all the lines are weird? Then I end up spamming the log or the console. That's bad too. I remember using an mp3 player that did exactly that, spamming the console when it hit a damaged MP3 header.

    So, if I don't care about the error, I ignore it. If I do care, and I still don't want to bail out at the sight of a single faulty line, I could stash away the error in, say, an array, and continue. Maybe I decide that I'd bail out after too many erronneous lines, and print out all the (unique) error messages afterwards.

    So, it's good that you aren't forced to receive the error return value. If you want to ignore a returned value, you can do this:

    result, _ = funcThatReturnsResultAndError()  
    _ = funcThatOnlyReturnsError()
    

    And for the record, if we have a func that returns two things, then

    a = funcReturns2Things()
    

    is a compiler error because you didn't receive the second value, regardless of the value type. It doesn't matter whether the second return value is an error type or a string or an int or whatever. It's illegal, because on the left side you have only one variable, and on the right side, the function returns two values. So you should do this:

    a, _ = funcReturns2Things()
    

    or this:

    a, b = funcReturns2Things()
    

    or this:

    funcReturns2Things()
    

    This is completely unrelated to whether go should force you to check the error values or not. I've never seen it stated or implied that go should force you to do that, and neither the compilers' behaviour hints at it. It doesn't. It shouldn't. There are good reasons for that, see above.

    (and actually, one could easily patch the go vet tool to print warnings for ignored error return values, but I don't think it would be very useful. People check their return values.)


    Continuing with exceptions, C# exceptions have a string message and a stack trace. Java exceptions have a string message and a stack trace. Go exceptions have a string message. No stack trace. Just the equivalent of a ToString(). So even if you do bubble your "someFunctionThatCouldFail() failed!" exception all the way to your topmost catch-all error handler/logger, it's going to have no idea where the exception actually came from. You could of course put some form of source line information manually in the message, but that still doesn't give you a stack trace of where it was called from. Good luck debugging!

    Go errors aren't exceptions. If you want a stacktrace, call the panic function, that's what it is for (and to leave a sinking ship, i.e. unrecoverable bug). But if you're writing a program that is gonna be presented to a user with little to no programming ability (and especially with no incentive to debug your code), then why show them a stacktrace at all? I, for one, am sick of all those python (and sometimes, GTK) programs on my computer that vomit a stacktrace whenever they do something stupid. Despite that I am capable of programming, I, as a user, do not want to debug or maintain other people's code (unless I'm forced to, by job or superior or if all else fails), so I don't want to look at overly verbose stacktraces, especially when a small, concise error message would have done the job.

    When you're writing a personal quick script, when you don't care much about correctness or errors? Sure, call panic, and utilize the hopefully rare stacktrace to debug your script. But don't expect the users of your programs to have the enthusiasm to debug your code (even if they have the ability to).

    As for tracking down problems based on error messages? Simple, have descriptive error messages, that have some key information, like what data caused the fault to happen (like what file failed to be opened, for example). This is why the error type is an interface: so that anything that has an Error() method can be returned as an error (and have the message printed). Meaning, you can stash away details about the error (like the filename, line number, if it was a parsing error for example) in a struct, and give it an Error() method. Within the calling code, use a type assertion to handle it as the correct type, so that you can access the fields of that struct. So you can both bubble up the error onto the topmost error handler, and still have all the details that you knew when the error happened. For debugging, use strategically placed fmt.Printf calls. Or call panic, if you desperately need a stacktrace.

    Remember, in go, error != exception.


    Go comes with a few libraries to help you get a basic HTTP server started...

    I'm afraid I can't say much about the stdlib's http server, or about handling HTML, for I haven't got much experience with it in go.


    When sending a response from an HTTP server...

    Skipping this one too, I don't know much about the stdlib http server.


    Another touted benefit of Go: You can compile binaries for all platforms from any platform! Except on Windows, it wants gcc to compile for Linux. Oops!

    Cross-compiling works for go code only, because in the toolchain, mostly only the go compilers are designed in a way to support sane cross-compiling. It would be non-trivial to get gcc to painlessly cross-compile for every supported platform, out of the box.

    Now, any go code that uses cgo (i.e. calls c code) has to use the gcc compiler to compile the C code. The last time I checked, the net package uses cgo, therefore if you're using net or net/http, etc, then you basically can't use cross-compiling, as far as I can tell. The root of the issue is that gcc is a PITA to set up to cross-compile, especially across operating systems where you have to supply the system specific libc header files and whatnot. I tried to cross-compile c code from linux to windows once. It's not a pleasant trip.
    But otherwise, this might give you some guidelines, even if you're trying to do it the other way around:


    Go has pointers. Go has interfaces. A type T can implement interfaces. A pointer to T, T*, ... can implement interfaces. Any interfaces it wants.

    • yes, you can declare a method that has a receiver of type *T, where T is some arbitrary type. What you said about this is true, so far.

    Totally unrelated to what are implemented by T.

    • yes, the method set of type T and the method set of type *T are different. It is considered bad practice to define methods on both types, so you should stick to only one of them. Read this:
      http://golang.org/doc/effectivego.html#pointersvs_values

    In fact, this is how the Exception interface works, it's implemented by pointers, not by actual types. That is, if you have a custom exception type MyException, then it's not MyException that implements the Exception class's ToString(), it's MyException*

    • This is factually wrong, and confuses some related concepts. You can implement an interface however you like. You can decide whether to use a pointer receiver or a value receiver. I.e. this compiles:
    
        package main
    
        type a int
    
        type b int
    
        func (a a) Error() string {
            return ""
        }
    
        func (b *b) Error() string {
            return ""
        }
    
        func main() {
            a := a(1)
            b := b(2)
            var err error
            err = a  // value receiver
            err = &b // pointer receiver
    
            // compile error, because type
            // b doesn't have a method Error() string
            // with a value receiver.
            // err = b
            _ = err
        }
    

    Related to the above, Go sometimes allows you to write foo.bar where you actually should write (*foo).bar Only sometimes, though. So if you want to be sure, you'd probably write it as the latter all the time just to be consistent.

    This is too vague for me to understand. I've been writing go code for some while now, and have written a 2k source lines long go program not so long ago, and I never had to write (*foo).bar, when foo is a pointer. Without anything specific, I can't really give a better answer. I don't really understand what you mean by "only sometimes".


    Want to initialize a variable? foo := bar

    Want to assign a variable a new value? foo = bar

    The compiler knows perfectly well when you assign a value to an uninitialized variable, so this annoying difference is totally cosmetic and pointless.

    False. Let's say that we don't need the colon, and a simple = can declare variables:

    foo = bar
    fooo = bar // typo, becomes declaration
    

    If the language didn't have shadowing, we would end up thinking about local variable names a lot more in order to use descriptive ones. Let's say there's a function called group(), and inside this same function, I want to use a local variable called group (and I don't intend to recursively call the group() function, inside itself). If there were no shadowing, I would have to think up another variable name. Shadowing isn't strictly necessary, but the same is true for good variable names. Or functions. Or structured programming. You can still program pretty much anything using arithmetic, pointers, goto and if statements just fine. Shadowing is convenient, if sometimes a bit confusing.


    On the subject of classes, there aren't any. There are structs. Remember how in C, you don't define member functions on a struct? Instead you define them outside and usually put the struct type as the first parameter? That's how it's done in Go too.

    False in a couple of details, true in others.

    • True, no classes. No type hierarchy either, i.e. no inheritance. That's by design, and with good reasons (see this, if you are curious: http://www.youtube.com/watch?v=sln-gJaURzk ). You can still have methods, though, and interfaces, and embedding (which is subtly but significantly different from inheritance).

    • You are confusing some terms. Let's draw a line between function and method. A function is, well, a thing that is callable, and potentially has parameters and return values. A method is the same as a function, except that it also has a thing called a receiver. Methods could be referred to as member functions I guess (I think that's what they call it in C++).

    • You can define a method on any type. See the example code I gave above? Those two methods are defined on two distinct int types (well, types that have the underlying type int - it's like a typedef from the c language). It's not possible to define them on the int type directly (because it's a built-in type, it's not local to the package), because that would lead to a huge mess in big programs (think about accidentally redefining methods). You can have your "member functions" on structs too, yes. And you can even define normal functions, of which the first parameter can be a struct, that is true too.


    Similarly, Go doesn't have constuctors. The convention to have a constructor for a type Foo is to make a static NewFoo method. Of course, this doesn't prevent the caller from ignoring it and calling new Foo() themselves, so when looking at API docs, always remember to check if a NewFoo method exists or not first! And if you're writing a class, better throw any guarantee that your object will be in a consistent state out of the window!

    Also false at places.

    • True, go doesn't have constructors, because constructors are just an unnecessary special case of functions. And in C++, for example, if you want to avoid using exceptions, but want to handle errors in a constructor, then you'd have to throw an exception, because you can't return values from constructors, because constructors are a special case of functions, implemented as a different language construct. It's just unneeded complexity. Instead in go, you just allocate your data with functions new() and make(). And maybe put them in functions which are called NewFoo and NewBar.

    • And yes, the convention is that either you create a dedicated function that returns a Foo value, or you don't, if it's not needed, for example if every field of the type is exported (i.e. publicly accessible). Read this:

    Of course, this doesn't prevent the caller from ignoring it and calling new Foo() themselves, so when looking at API docs, always remember to check if a NewFoo method exists or not first!

    • And not entirely true. Let's have these types:

      type Foo struct { nonexported int Exported int }

      type Bar struct { nonexported1 string nonexported2 byte }

    Both Foo and Bar are exported types (they are capitalized), but not every one of their fields are exported (the lowercase initial lettered ones are unexported). Therefore, if you want to use Foo and Bar outside the package they were declared in, you can certainly call

    f := new(Foo)
    b := new(Bar)
    

    But you can't really access their fields, or not all of them. You can't set their unexported fields from outside the package they're declared in. That means the caller code is pretty limited in what they can do with Foo and Bar. Therefore, it's conventional to provide a function that takes the necessary parameters to create Foo or Bar and return the values, properly parametrized. Your claim about inconsistent state is false. If you don't want a field to be accessed outside the package, don't export it.


    Function overloading isn't allowed either. I sure hope you only need one set of parameters for your constructor!

    Name your function differently. For example, in the os package there's

    http://golang.org/pkg/os/#Create



    Each of them can be thought of as a "constructor" (sort of), but all four of them behave differently, take and give different parameteres, and have different names. No need for function overloading. It simplifies the language and speeds up the method lookup when using interfaces, I think.


    No generics either.

    True. http://golang.org/doc/faq#generics


    There's a printf (format string, varargs). There's a println (single string). There's no println that takes a format string and varags, so you have to write "\n" in all of your println's, since 99% of the usages of println are for writing lines.

    the uncapitalized print and println functions (without any package qualifier either) are there for bootstrapping purposes. They aren't intended for general usage, they're there to build the go environment more easily. Use the fmt package instead. See these:





    Just like the C++ STL, there's no rational thought put behind when something should be a member function and when something should be a static function on the type. So methods like string.Contains are actually static methods on the strings type.

    in go, there is no such thing as a static method. There are only functions and methods. Methods are just functions but with a receiver too, not just a regular parameter list and a return value list. Function example:

    func f(a, b int) int {
        return a+b
    }
    a := f(1, 2)
    

    Method example:

    type WTF string
    
    func (daily WTF) Post(title string) string {
        return fmt.Sprintf("%s\n\n%s\n", title, daily)
    }
    
    var lolwut WTF = "herp derp i can haz softcoding"
    lolwut.Post("Soft Coding")
    

    The go lang developers were careful not to define any methods on built-in types, by design. That is because it keeps the type system clean and simple. This is why the functions len() and cap() are functions instead of methods.

    There exists the strings (<-plural, so it doesnt collide with the type name, written in singular) package, it has functions in it. They're functions mostly, and they operate on the built-in string type, because if they were methods, they'd need to declare a new type that has an underlying type of string. And then in order to use the strings package, you'd have to convert your string into that string.OtherString type before you can call the methods on it. With functions, you don't need to do that.

    As for what's a function and what's a method and why, is based on good taste thinking about the implications. There are several reasons why you might want a method instead of a function:

    • you want to implement an interface
    • you want to put the code in a separate namespace (specific to the type you declare it on)
    • you want to couple the code more tightly to the type you declare the method on
    • etc. Search the golang-nuts mailing list for more.

    Speaking of strings, how do you append a character to a string? First you have to call a method to convert the character to a string. Then you call a method to append the new string to the original string.

    Umm.

    // let's declare some characters.
    ch1 := 'p'
    ch2 := 'e'
    // declare string
    str := "no"
    // type conversion necessary.
    str += string(ch1) + string(ch2)
    

    "Go doesn't implicitly anything."


    Go is supposed to enforce strict whitespacing via compiler errors, and some of them indeed are. Good. (This isn't about the gofmt tool.) But a lot of them aren't. Bad. It's very inconsistent on what it does think is an error and what isn't.

    False. The go compiler doesn't enforce whitespace conventions at all, it's not supposed to. It does something related, though, namely, it automatically inserts semicolons into the code where it's appropriate, so that the later compiler passes can parse it. From what I hear, it's much better than the javascript semicolon rule, but I don't use js, so idk. A side-effect of this:

    // legal
    if true {
        fmt.Println("hi mom")
    }
    // after semicolons inserted:
    if true {
        fmt.Println("hi mom");
    }
    
    // illegal
    if true
    {
        fmt.Println("nopenopenope")
    }
    // after semicolons inserted:
    if true; // semi-colon tumor
    {
        fmt.Println("nopenopenope");
    }
    // and this is why the compiler
    // would complain about a missing
    // true-branch after the if or something
    

    “From the tutorial: "The language forces the brace style to some extent.” Well, that’s it. If I can’t have a brace-war tearing the dev group apart for months and kill productivity, I want nothing to do with that language.“ — SoftwareMaven in hackernews

    There are some other deviations from the mainstream C-like syntax, like forcing blocks after if, for, etc; there's no while, no try-catch-finally, and instead defer, panic and recover, etc etc.


    Windows is pretty much a third-class citizen right now. For example, a function that takes in a path string and returns the path after resolving all symlinks returns the input path unmodified on Windows. Looking at the source commits, there is a commit that claims to have fixed it but actually didn't.

    I wrote a prototype text editor (as in, the actual text control part, and the inmemory editor too) in go, using Go-SDL (an sdl binding for go). SDL is a low-level c graphics/gaming library. The program featured some network code and heavily utilized concurrency (it was a collab text editor prototype, like what Google Docs does).
    When I tried building it in windows, I had to do the following: install git (PITA), install hg (hgtortoise was easier to install), install mingw (a gcc package for windows, wasn't too hard to install), install sdl binaries and put them into the proper mingw directories and figure out how how to make pkg-config handle them (major PITA). After all this, I finally installed the go toolchain on windows. This last part was the least painful of all. Then I built the code and started debugging, trying to figure out why the hell the otherwise portable graphics (to restate, written in C) are producing a bug I haven't seen on linux. It took me 2 days to figure out (the fix was related to locking sdl code to a single os thread).
    I wouldn't call windows a third-class citizen after this.

    Most of your opinions and criticism you have cited here aren't based on facts, and some of them stem from assumptions you've most likely brought along from other languages. I suggest that before you make observations like these, check your facts, check the compiler, and maybe read stuff about go on reddit, hackernews, or the mailing list. Lots of these points were discussed at length already.

    I hope I wasn't too offensive or condescending with my answers or my style of giving them.



  • @Arnavion said:

    • Want to initialize a variable? foo := bar

      Want to assign a variable a new value? foo = bar

      The compiler knows perfectly well when you assign a value to an uninitialized variable, so this annoying difference is totally cosmetic and pointless.

      (Yes, I know it's significant if you want to make a new variable to shadow one in an outer scope. Personally I'd rather that shadowing wasn't allowed at all. I've never come across any instance in all my software dev life where shadowing was _necessary_.)

    Well, in most other languages you have to declare the variables. The : is what makes it a declaration in Go. I kind of like the Python approach though—if variable is assigned in function, than it is local. Python does not have smaller scope than function, though. Well, since Go does not have RAII support, it does not have much use for block scope either anyway. @Arnavion said:
    • On the subject of classes, there aren't any. There are structs. Remember how in C, you don't define member functions on a struct? Instead you define them outside and usually put the struct type as the first parameter? That's how it's done in Go too.
    I would actually say this is not a WTF at all. In fact more new languages do this, because it has plenty of advantages and no disadvantages. As long as the methods declared outside the type can implement interfaces and so participate in polymorphism (which they can), there is no disadvantage and the advantage is that you can implement interface for existing type which other languages workaround with various kludges (in C++ templates often call free overloaded functions, C# has those funny extension methods, Scala implicitly creates wrappers etc.). @Arnavion said:
    • Similarly, Go doesn't have constuctors. The convention to have a constructor for a type Foo is to make a static NewFoo method. Of course, this doesn't prevent the caller from ignoring it and calling new Foo() themselves, so when looking at API docs, always remember to check if a NewFoo method exists or not first! And if you're writing a class, better throw any guarantee that your object will be in a consistent state out of the window!
    Indeed a huge WTF. Well, you are supposed to only export the interfaces from the module, so than nobody can construct the object in any other way than via the provided factory. But than they can't really "inherit" (the automatic-cast-to-member does work like inheritance) it either... Plus the fact it does not have destructors either, which I believe is a much bigger problem (I am not going to take language without RAII seriously). @Arnavion said:
    • No generics either.
    Now this is TRWTF. A strongly, statically typed language without at least generics really shouldn't be taken seriously (note: C is weakly typed). At least generics the Java way, though C++ templates are much stronger than that, more like hygienic macros, and other new languages like D or Rust have even stronger templates. @Arnavion said:
    • Just like the C++ STL, there's no rational thought put behind when something should be a member function and when something should be a static function on the type. So methods like string.Contains are actually static methods on the strings type.
    I would think the way methods are done is in part to remove this distinction. But apparently it isn't.

    On a side note: In C++, std::string::substr is actually one of those methods that should have been free functions instead. But there it would be a generic function searching a subsequence in sequence. Which in Go it apparently isn't. @Arnavion said:

    • Go is supposed to enforce strict whitespacing via compiler errors, and some of them indeed are. Good. (This isn't about the gofmt tool.) But a lot of them aren't. Bad. It's very inconsistent on what it does think is an error and what isn't.
    I would call the way they did this a WTF. The newline-terminates-statement-if-it-can comes from JavaScript, but most people consider it a huge mistake there as it is done there. Since than it is copied by several languages, slightly inconsistently in each.

    Now the full white space significance done in Python or Haskell turns out really saving a bit of typing and makes the code clearer. But this half-baked option only making newlines significant, I don't think so. @Arnavion said:

    … and the channels are very nice.
    I would actually call channels a serious WTF. It really goes together with the lack of generics. Things like channels, or associative collections, the other thing go builds in, are sufficiently complex that they may need to be evolved, or special versions may be needed in some environment or something. If they were done in a library it would be much better, but it would require generics. I am all for special syntax for them, but think the implementation really shouldn't be tied to the language. For example in python—it has special syntax for list and dictionary, but they are library types and you can implement your own type that behaves the same, except for the syntax support. And C++ even lately introduced some syntax support for any kind of collection. But in Go, no way.

  • Discourse touched me in a no-no place

    @paperwing said:

    In concurrent code, it's much harder to use exceptions safely and properly
    Unwinding the logical stack is hard? Enforcement through a system of checked exceptions is hard? (Checked exceptions are verbose and officious though.)
    @paperwing said:
    But if you're writing a program that is gonna be presented to a user with little to no programming ability (and especially with no incentive to debug your code), then why show them a stacktrace at all?
    Showing a stack trace to a user is typically proof that the program is unfinished; I've never met a user who could do anything useful with a stack trace. (Logging the trace and keeping going though, that's much more useful behavior, especially in server apps.)
    @paperwing said:
    I hope I wasn't too offensive or condescending with my answers or my style of giving them.
    You were too long. Far far too long. Expect people to skip reading what you wrote.



  • @paperwing said:

    They are a piece of memory with type and value and whatnot. They're variables that can be printed or otherwise reported to the user (or the caller code).

    You mean like Exceptions?

    @paperwing said:

    you are not forced to handle the error in an error-handling-only codepath (in contrast, try and catch branches enforce exactly this), meaning you can even have multiple errors in flight at the same time (i.e. store them in different variables), and be able to do other stuff before you handle or return the errors. In contrast, when you catch an exception, you have to handle it there and then (or re-throw it).

    You know how I know you haven't ever used exceptions before?
    It's because you don't know what you're talking about.

    @paperwing said:

    you don't have to play the "what state gets invalidated when an exception is thrown here" game, because you explicitly see what happens in case of error. Handling returned errors is explicit, while letting exceptions fall-through is implicit. When you're debugging and trying to figure out what code caused your data to be invalid, and you're looking at the source, you don't explicitly see the exception that had caused some code not to run, or had caused some code not to be undone. Especially if you've caught that exception in an upper level than where the data was invalidated.

    If you're writing code that needs explicit cleanup routines, you are doing something very wrong.
    Even C++ has ways of writing programs without manual cleanup, and C++ is more than two decades old.

    @paperwing said:

    In concurrent code, it's much harder to use exceptions safely and properly, because if you use it in case of any kind of invalid behaviour, then you have to be very careful that every thread (or goroutine, in go's case) is guarded so that none of them can tear down the whole program, only if explicitly necessary.

    What? If you don't know that a particular exception could be thrown and it does, you have no way of ensuring that the program is even able to recover from it. The same is true of error returns: if part of your program starts encountering a situation you never expected and coded for, the program should stop.
    You can't recover from an error you did not expect. This applies to both exceptions and error returns.

    @paperwing said:

    For a less silly example, what if I'm processing a big file line-by-line, and one line has weird data in it that I don't care about, because all the other lines are still valid? I can't stop for one measely line that has a misplaced semicolon in it. So I ignore the error. As long as the processing function recognises the invalid data and doesn't corrupt the program state, it's cool. And what if I do report the error? Well, what if all the lines are weird? Then I end up spamming the log or the console. That's bad too. I remember using an mp3 player that did exactly that, spamming the console when it hit a damaged MP3 header.

    So, if I don't care about the error, I ignore it. If I do care, and I still don't want to bail out at the sight of a single faulty line, I could stash away the error in, say, an array, and continue. Maybe I decide that I'd bail out after too many erronneous lines, and print out all the (unique) error messages afterwards.

    int errors = 0;
    for(string line in file)
    {
        try
        {
            ParseLine(line);
        }
        catch(LineParseException ex)
        {
            PrintException(ex);
            errors++;
            if(errors == 10)
            {
                throw new YourFileIsFuckedException();
            }
        }
    }
    

    @paperwing said:

    But if you're writing a program that is gonna be presented to a user with little to no programming ability (and especially with no incentive to debug your code), then why show them a stacktrace at all?

    A good question. I'd like to know who does that, too. Certainly not any program on Windows:

    @paperwing said:

    But don't expect the users of your programs to have the enthusiasm to debug your code (even if they have the ability to).

    The fact that you think programmers expect users to debug their own program shows just how insane you really are.

    @paperwing said:

    For debugging, use strategically placed fmt.Printf calls.

    Here, have a purple dildo. Now go fuck yourself with it; this is 2013, not 1980.

    @paperwing said:

    You are confusing some terms. Let's draw a line between function and method. A function is, well, a thing that is callable, and potentially has parameters and return values. A method is the same as a function, except that it also has a thing called a receiver. Methods could be referred to as member functions I guess (I think that's what they call it in C++).

    Yes yes, and malware isn't a virus. No one gives a shit.

    @paperwing said:

    True, go doesn't have constructors, because constructors are just an unnecessary special case of functions. And in C++, for example, if you want to avoid using exceptions, but want to handle errors in a constructor, then you'd have to throw an exception, because you can't return values from constructors, because constructors are a special case of functions, implemented as a different language construct. It's just unneeded complexity. Instead in go, you just allocate your data with functions new() and make(). And maybe put them in functions which are called NewFoo and NewBar.

    myTarget := new(MissileTarget)
    myMissileSilo.LaunchAt(myTarget) // Congrats, you have just nuked the Gulf of Guinea

    @paperwing said:

    False. The go compiler doesn't enforce whitespace conventions at all, it's not supposed to. It does something related, though, namely, it automatically inserts semicolons into the code where it's appropriate, so that the later compiler passes can parse it.

    “From the tutorial: "The language forces the brace style to some extent.”

    False, by your own words. Newlines are significant and are enforced by the compiler.



  •  I have a question about this alleged compilation WTF:

    Can you compile on Windows for Windows, using MinGW or MSYS?

    • If yes, then not a WTF. 
    • If no however, and if you can't compile on MSVC either, I guess it means the only way to compile for Windows is cross-compiling from Linux. In which case, that's a WTF indeed.


  • @paperwing said:

    There's a difference between errors (aka error messages) and exceptions. So, I ask you to refer to errors as errors, and to exceptions as exceptions.

    I was lazy on the correct nomenclature, yes.

    @paperwing said:

    About verbosity. Yes, checking errors manually does seem more verbose than relying on exception handling; where you just ignore the the fact that any line of code can potentially throw an exception, and let the caller handle it. If you haven't got used to it yet, then writing all those ifs and returns is a bit tiresome, but they're also readable.

    The only other place I've seen code that uses errors is C, and there it's almost always abstracted away using macros like the ones I mentioned earlier.

    @paperwing said:

    There are several arguments for and against both returning errors and exception handling, but I'm not going to discuss all of them here.

    I'm not against error codes if they don't require 3 lines per function call.

    @paperwing said:

    No, go is not supposed to force you to check for returned errors.

    @paperwing said:
    This is completely unrelated to whether go should force you to check the error values or not. I've never seen it stated or implied that go should force you to do that, and neither the compilers' behaviour hints at it. It doesn't. It shouldn't.

    You misunderstood what I meant by "forces to check". The fact that
    @paperwing said:
    And for the record, if we have a func that returns two things, then

    a = funcReturns2Things()
    

    is a compiler error because you didn't receive the second value, regardless of the value type.

    is exactly what I was talking about. The programmer is _forced_ to not ignore that an error might be returned. He cannot call funcReturns2Things() without atleast making a token effort to receive the possible error.

    This example is almost identical to the one I gave in the OP, by the way. And it breaks horribly if you have a funcThatReturns1Thing that only returns an error. Again, the example is in the OP.
    @paperwing said:

    (and actually, one could easily patch the go vet tool to print warnings for ignored error return values, but I don't think it would be very useful. People check their return values.)

    People check their return values, except when they don't. The whole point of a vetting tool (although I'd rather this be a compiler error) is to avoid making mistakes. "People usually don't make this mistake" is not an argument against this.

    @paperwing said:

    If you want a stacktrace, call the panic function, that's what it is for (and to leave a sinking ship, i.e. unrecoverable bug).

    @paperwing said:
    Or call panic, if you desperately need a stacktrace.

    The bug is not necessarily unrecoverable. I still want to see its strack trace.

    @paperwing said:

    But if you're writing a program that is gonna be presented to a user with little to no programming ability (and especially with no incentive to debug your code), then why show them a stacktrace at all?

    The "user" is me, or more specifically, a top-level catch-all error handler+logger. The reason I want to see the stack trace is because I want to log all unhandled errors.

    @paperwing said:

    As for tracking down problems based on error messages? Simple, have descriptive error messages, that have some key information, like what data caused the fault to happen (like what file failed to be opened, for example).

    This doesn't give me a stack trace.

    @paperwing said:

    This is why the error type is an interface: so that anything that has an Error() method can be returned as an error (and have the message printed). Meaning, you can stash away details about the error (like the filename, line number, if it was a parsing error for example) in a struct, and give it an Error() method.

    I already mentioned that I could add the filename and line number in the error message in the OP. It still doesn't give me a stack trace.

    @paperwing said:

    Cross-compiling works for go code only, because in the toolchain, mostly only the go compilers are designed in a way to support sane cross-compiling. It would be non-trivial to get gcc to painlessly cross-compile for every supported platform, out of the box.

    Now, any go code that uses cgo (i.e. calls c code) has to use the gcc compiler to compile the C code. The last time I checked, the net package uses cgo, therefore if you're using net or net/http, etc, then you basically can't use cross-compiling, as far as I can tell. The root of the issue is that gcc is a PITA to set up to cross-compile, especially across operating systems where you have to supply the system specific libc header files and whatnot. I tried to cross-compile c code from linux to windows once. It's not a pleasant trip.
    But otherwise, this might give you some guidelines, even if you're trying to do it the other way around:
    https://code.google.com/p/go-wiki/wiki/WindowsCrossCompiling

    I understand. As I said, I was simply miffed that their grandiose advertisement of the ease of cross-compiling was a lie.

    @paperwing said:

    This is factually wrong, and confuses some related concepts.

    Fair enough. I see in my port that I've defined the Error() method on MyException* instead of MyException. I don't remember what prompted me to do this, so I won't argue that errors are implemented this way. The fact that pointers can implement interfaces still stands, though.

    @paperwing said:

    This is too vague for me to understand. I've been writing go code for some while now, and have written a 2k source lines long go program not so long ago, and I never had to write (*foo).bar, when foo is a pointer. Without anything specific, I can't really give a better answer. I don't really understand what you mean by "only sometimes".

    I see this mentioned in my notes but I don't see a corresponding example in the actual code, so once again I'll concede on this point.

    @paperwing said:

    False. Let's say that we don't need the colon, and a simple = can declare variables:

    foo = bar
    fooo = bar // typo, becomes declaration
    

    Fair point.

    @paperwing said:

    If the language didn't have shadowing, we would end up thinking about local variable names a lot more in order to use descriptive ones. Let's say there's a function called group(), and inside this same function, I want to use a local variable called group (and I don't intend to recursively call the group() function, inside itself). If there were no shadowing, I would have to think up another variable name.

    I personally would never approve code that uses a variable named group inside a method named group.

    @paperwing said:

    Shadowing isn't strictly necessary, but the same is true for good variable names. Or functions. Or structured programming. You can still program pretty much anything using arithmetic, pointers, goto and if statements just fine.

    Straw man.

    @paperwing said:

    You are confusing some terms. Let's draw a line between function and method.

    Lazy on the nomenclature, again.

    @paperwing said:

    True, go doesn't have constructors, because constructors are just an unnecessary special case of functions.

    They are.
    @paperwing said:
    And in C++, for example, if you want to avoid using exceptions, but want to handle errors in a constructor, then you'd have to throw an exception, because you can't return values from constructors, because constructors are a special case of functions, implemented as a different language construct.

    Fine so far.

    @paperwing said:

    Instead in go, you just allocate your data with functions new() and make(). And maybe put them in functions which are called NewFoo and NewBar.

    This is exactly the problem I mentioned in the OP. For any struct with a non-trivial initialization, NewFoo is going to have a bunch of code in it. If I define a NewFoo, I would want the programmer to be unable to call new(Foo). But in Go, even if I make NewFoo, the programmer can still call new(Foo) and get a Foo instance that hasn't run the code in NewFoo. Hence my point about throwing invariants out of the window.

    @paperwing said:

    But you can't really access their fields, or not all of them. You can't set their unexported fields from outside the package they're declared in. That means the caller code is pretty limited in what they can do with Foo and Bar.

    They've created a Foo and a Bar which have nil for their fields. If I had designed the struct in mind such that the fields should never be nil, that invariant has been broken.

    @paperwing said:

    Your claim about inconsistent state is false. If you don't want a field to be accessed outside the package, don't export it.

    Nothing to do with the visibility of the fields, as I've already explained above.

    @paperwing said:

    Name your function differently.

    Yes, this is the standard argument against function overloading, where the argument list is merged into the function name. This works fine for regular functions like the examples you gave, but this doesn't work for constructors, since the convention for the constructor of Foo is NewFoo. You say Create, NewFile, Open and OpenFile can all be thought of as constructors, but this is a bad example. In the case of files, it is reasonable to expect the programmer to think about "opening" or "creating" as synonyms that return a file object. However in the case of the type Foo, you'd have to go with NewFoo, MakeFoo, CreateFoo, InstantiateFoo. This gets ridiculous pretty fast.

    The alternative as I said is to merge the argument list into the function name. NewFooFromDatabaseRow, NewFooFromUserInput, NewFooFromFile. Ugh.

    @paperwing said:

    the uncapitalized print and println functions (without any package qualifier either) are there for bootstrapping purposes. They aren't intended for general usage

    I was talking about fmt.Printf and fmt.Println. I was not giving valid Go examples (C-like pseudocode).
    Also there's a typo in there. I meant to say that the programmer would have to put "\n" in all his printf calls (not println).

    @paperwing said:

    in go, there is no such thing as a static method.

    Free functions in go are the equivalent of static methods in say, C#, hence I called them that.

    @paperwing said:

    There exists the strings (<-plural, so it doesnt collide with the type name, written in singular) package, it has functions in it. They're functions mostly, and they operate on the built-in string type, because if they were methods, they'd need to declare a new type that has an underlying type of string. And then in order to use the strings package, you'd have to convert your string into that string.OtherString type before you can call the methods on it. With functions, you don't need to do that.

    Or they could just be defined as methods of the string type. As for...

    @paperwing said:

    The go lang developers were careful not to define any methods on built-in types, by design. That is because it keeps the type system clean and simple. This is why the functions len() and cap() are functions instead of methods.

    I disagree that it makes it clean and simple.

    @paperwing said:

    As for what's a function and what's a method and why, is based on good taste thinking about the implications. There are several reasons why you might want a method instead of a function:

    Perhaps I'm very OOP in my thinking, but I'd rather have my methods scoped to the type they work on (C# way), rather that be free functions that can work on many types from disparate inheritance trees (C++ STL).

    @paperwing said:

    // let's declare some characters.
    ch1 := 'p'
    ch2 := 'e'
    // declare string
    str := "no"
    // type conversion necessary.
    str += string(ch1) + string(ch2)

    "Go doesn't implicitly anything."

    Fair enough, I have this written in my notes too but I solved the problem that required me to append the character another way, so I don't have the code any more to see why I thought it was harder.

    @paperwing said:

    False. The go compiler doesn't enforce whitespace conventions at all, it's not supposed to.

    I really should've written down examples in my notes...

    @paperwing said:

    I wrote a prototype text editor (as in, the actual text control part, and the inmemory editor too) in go, using Go-SDL (an sdl binding for go). SDL is a low-level c graphics/gaming library. The program featured some network code and heavily utilized concurrency (it was a collab text editor prototype, like what Google Docs does).

    When I tried building it in windows, I had to do the following: install git (PITA), install hg (hgtortoise was easier to install), install mingw (a gcc package for windows, wasn't too hard to install), install sdl binaries and put them into the proper mingw directories and figure out how how to make pkg-config handle them (major PITA). After all this, I finally installed the go toolchain on windows. This last part was the least painful of all. Then I built the code and started debugging, trying to figure out why the hell the otherwise portable graphics (to restate, written in C) are producing a bug I haven't seen on linux. It took me 2 days to figure out (the fix was related to locking sdl code to a single os thread).

    Okay.
    @paperwing said:
    I wouldn't call windows a third-class citizen after this.

    I'm not sure how "It was a pain to install tooling on Windows. SDL had a bug on Windows." translates to "Go's Windows support is on par for its support for Linux."

    @paperwing said:

    Most of your opinions and criticism you have cited here aren't based on facts, and some of them stem from assumptions you've most likely brought along from other languages. I suggest that before you make observations like these, check your facts, check the compiler, and maybe read stuff about go on reddit, hackernews, or the mailing list. Lots of these points were discussed at length already.

    Some of the fault is mine for not being precise in my terms. Three of my points I cannot substantiate. The rest still hold, I believe.

    @paperwing said:

    I hope I wasn't too offensive or condescending with my answers or my style of giving them.

    No problem.


  • Discourse touched me in a no-no place

    @Salamander said:

    myTarget := new(MissileTarget)

    myMissileSilo.LaunchAt(myTarget) // Congrats, you have just nuked the Gulf of Guinea

    You realize, according to our records that spot is the most biodiverse location on the planet. Nowhere else do you find reported instances of orangutans, polar bears and kangaroos in the same location! Won't someone think of the poor animals! Don't nuke them!



  •  When I learn't how Interfaces work in Go, I knew it wasn't ever going to be a language that I would like.



  • @Arnavion said:

    Besides, somehow, I don't think anyone would want to run a Linux binary compiled using gcc on Windows on their servers. I certainly wouldn't.

    Why not?  Given the same target headers and libs, a GCC cross-compiler should produce the exact same output as a native version.  Do you have some germphobia-like fear that the Linux binary will somehow be contaminated by having been near Windows?




  • @DaveK said:

    @Arnavion said:

    Besides, somehow, I don't think anyone would want to run a Linux binary compiled using gcc on Windows on their servers. I certainly wouldn't.

    Why not?  Given the same target headers and libs, a GCC cross-compiler should produce the exact same output as a native version.  Do you have some germphobia-like fear that the Linux binary will somehow be contaminated by having been near Windows?


    You can't take a Linux binary across different distro families, what makes you think you can take it to a Linux from a completely non-Unix system?



  • @Arnavion said:

    @DaveK said:
    Just like PHP or perl-cgi then?

    Is there a technical reason for implementing it this way? Because it seems backwards to me.

    PHP lets you set the HTTP status code at any time before you start content (the part after the headers) output. It can be before, after, or even sandwiched inbetween the headers.

    Perl CGI I've never looked at before, but a quick trip to the docs makes it seem like you set the status code by just mixing a header named status in with the other headers.


  • ♿ (Parody)

    @MiffTheFox said:

    You can't take a Linux binary across different distro families, what makes you think you can take it to a Linux from a completely non-Unix system?

    That's not really true. It depends on what your dependencies are. Lots of binaries are deployed to all sorts of distros.


  • Discourse touched me in a no-no place

    @MiffTheFox said:

    @DaveK said:

    @Arnavion said:

    Besides, somehow, I don't think anyone would want to run a Linux binary compiled using gcc on Windows on their servers. I certainly wouldn't.

    Why not?  Given the same target headers and libs, a GCC cross-compiler should produce the exact same output as a native version.  Do you have some germphobia-like fear that the Linux binary will somehow be contaminated by having been near Windows?


    You can't take a Linux binary across different distro families, what makes you think you can take it to a Linux from a completely non-Unix system?

    Which part of cross-compile did you miss?


  • @boomzilla said:

    @MiffTheFox said:
    You can't take a Linux binary across different distro families, what makes you think you can take it to a Linux from a completely non-Unix system?

    That's not really true. It depends on what your dependencies are. Lots of binaries are deployed to all sorts of distros.

    Doesn't installing your binary and all its dependencies into /opt/[your_program] always work on any distro?



  • Goddamned man. It's Monday morning and I haven't even had my coffee yet.



  • @paperwing said:

    Stuff about exceptions
    In this case, errors are much more verbose than exceptions, and has no advantages over exceptions.  Exceptions are cleaner and easier to code against.  You can still catch errors and deal with them the same way, and you don't have to bubble them up in a critical path.  They're also harder to forget.  Exceptions are easy to use in concurrent code, and (at least in C#) it's easy to know what state is valid (as long as you don't do something like modify the parameters in the method).

    @paperwing said:

    For a less silly example, what if I'm processing a big file line-by-line, and one line has weird data in it that I don't care about, because all the other lines are still valid? I can't stop for one measely line that has a misplaced semicolon in it. So I ignore the error. As long as the processing function recognises the invalid data and doesn't corrupt the program state, it's cool. And what if I do report the error? Well, what if all the lines are weird? Then I end up spamming the log or the console. That's bad too. I remember using an mp3 player that did exactly that, spamming the console when it hit a damaged MP3 header.

    You should be doing validation on your lines as you process them, not blindly processing and then throwing an exception when it fails.  Or if a line being formatted correctly is an exceptional case, then you can catch that exception, possibly store it somewhere, and continue.  Errors has no benefits over this.

    @paperwing said:

    But if you're writing a program that is gonna be presented to a user with little to no programming ability (and especially with no incentive to debug your code), then why show them a stacktrace at all?
    As has already been pointed out, you have this idea that if you throw an exception, it will eventually be shown to the user.  This couldn't be further from the truth.  Maybe quit using crappy programs in a crappy OS? ;)

    @paperwing said:

    You can implement an interface however you like. You can decide whether to use a pointer receiver or a value receiver
    What's the purpose of having a pointer to a type inherit an interface?  What is the "thing" that is actually implementing it?  You might be able to convince me that this is a good idea, but I certainly can't see why it ever would be.

    @paperwing said:

    If the language didn't have shadowing, we would end up thinking about local variable names a lot more in order to use descriptive ones.
    Shadowing is one of the worst language features you can possibly have.  You don't have method overloading, but you have variable shadowing??  You would have to think about different names?? How long are your methods that you need so many similar variables?

    @paperwing said:

    Shadowing is convenient, if sometimes a bit confusing.
    So to you, it lets you program faster with more bugs?

    @paperwing said:

    True, no classes. No type hierarchy either, i.e. no inheritance. That's by design, and with good reasons (see this, if you are curious: <font color="#698d73">http://www.youtube.com/watch?v=sln-gJaURzk</font> ). You can still have methods, though, and interfaces, and embedding (which is subtly but significantly different from inheritance).

    You're going to have to do better to convince me that no classes and no type hierarchy is a good thing without saying "watch this hour long video."

    @paperwing said:

    You are confusing some terms.
    Well, no, he's not.  From Wikipedia: "In different programming languages a subroutine may be called a procedure, a function, a routine, a method, or a subprogram."  The difference can be summarized as a static function/method or an instance function/method.

    @paperwing said:

    You can have your "member functions" on structs too, yes. And you can even define normal functions, of which the first parameter can be a struct, that is true too.
    Ok, so you can still define your functions related to the class inside the class?  That's good, although the syntax for declaring an instance method is pretty verbose.

    @paperwing said:

    True, go doesn't have constructors, because constructors are just an unnecessary special case of functions.
    Er, no, they're not.  They define what happens when you create a new instance of an object.  If anybody can call "new foo()" and you don't get to define what happens when they do that, then (as he said) you have no way to guarantee a consistent state of the object.

    @paperwing said:

    Your claim about inconsistent state is false.
    No it's true, see above.

    @paperwing said:

    Name your function differently. For example, in the os package there's
    Oh, so you can be extremely creative when naming functions (where you can actual need a lot of similarly/identically named things) but not with variables? 

    @paperwing said:

    It simplifies the language and speeds up the method lookup when using interfaces, I think.
    First off, if you don't know that it speeds up the method lookup, then don't say that it does.  Second, has anyone had a performance problem with languages doing method lookups?  Then it's not a problem that needs fixing.  Third, it doesn't simply the language at all.  If I want to print something, I'll type "Print" and look at the different lists of arguments that it can take, easy as pie.  In your method, I have to know ahead of time the different name that matches the arguments I want to give it.  Not easy.

    @paperwing said:

    the uncapitalized print and println functions (without any package qualifier either) are there for bootstrapping purposes.
    I wanted to see in the docs for printf and println if it said "don't use these".  It doesn't. 

    @paperwing said:

    The go lang developers were careful not to define any methods on built-in types, by design. That is because it keeps the type system clean and simple.
    How in the world could you possibly think that?? 

    @paperwing said:

    str += string(ch1) + string(ch2)

    "Go doesn't implicitly anything."

    I understand why, but there are some things that are good to have done implicitly.  Char->string conversion is one of them.



  • @paperwing said:

    Name your function differently.
    whoosh OpenGL is probably the best example of why that statment is bullshit!
    @Sutherlands said:
    Then it's not a problem that needs fixing.
    THIS is the problem with go; they see all these "problems" that aren't really problems and don't need fixing and they go ahead and "fix" the "problem."



    Their FAQ states that go prioritizes optimizing the compile time, compilation time could have been a problem in the past, but not in 20-fucking-13! This is just the tip of the shit iceberg.



    Even if go has a few diamonds buried in that shitberg, being forced to program in that horrible abomination isn't worth it. If there are any good ideas in go (and that's a huge fucking if) they need to be extracted and put to use in a sane language. It kind of reminds me of that 200mph gyro bike from soutpark, sure its fast but it holds you in via a dildo inserted into the anus http://www.southparkstudios.com/clips/153051/flexi-grips



  • If you think Go is crazy, wait 'til you try Scala. I knew things weren't going to work between us after reading the variables chapter.



  • @ubersoldat said:

    If you think Go is crazy, wait 'til you try Scala. I knew things weren't going to work between us after reading the variables chapter.

    • is a method on the Int type.

    There's a type called Nothing that has no legal values that is a subtype of every type.



  • @Ben L. said:

    @ubersoldat said:
    If you think Go is crazy, wait 'til you try Scala. I knew things weren't going to work between us after reading the variables chapter.

    • is a method on the Int type.

    There's a type called Nothing that has no legal values that is a subtype of every type.

    Neither of those are unusual features in functional languages.



  • @Medinoc said:

    Can you compile on Windows for Windows, using MinGW or MSYS?

    I only have MSVC installed, but AFAICT go doesn't spawn any cl.exe processes. I'm not sure how it actually compiles, but it does. Regardless of how it does it, you can indeed compile for Windows on Windows.



  • @Arnavion said:

    I only have MSVC installed, but AFAICT go doesn't spawn any cl.exe processes. I'm not sure how it actually compiles, but it does.

    Whoa.

    Whoa.

    You need GCC to compile Go. That's a constant. You don't need GCC to run Go compilers (unless you're using the clusterfuck that is cgo, but that's a topic for another thread).

    Are you under the impression that cl.exe does something magical that only it can do to compile for Windows?



  • @Ben L. said:

    @Arnavion said:
    I only have MSVC installed, but AFAICT go doesn't spawn any cl.exe processes. I'm not sure how it actually compiles, but it does.
    Whoa.

    Whoa.

    You need GCC to compile Go. That's a constant. You don't need GCC to run Go compilers (unless you're using the clusterfuck that is cgo, but that's a topic for another thread).

    Are you under the impression that cl.exe does something magical that only it can do to compile for Windows?

    I'm not sure how you managed to interpret what I wrote that way... or what I was responding to...



  • @Arnavion said:

    @Ben L. said:

    @Arnavion said:
    I only have MSVC installed, but AFAICT go doesn't spawn any cl.exe processes. I'm not sure how it actually compiles, but it does.

    Whoa.

    Whoa.

    You need GCC to compile Go. That's a constant. You don't need GCC to run Go compilers (unless you're using the clusterfuck that is cgo, but that's a topic for another thread).

    Are you under the impression that cl.exe does something magical that only it can do to compile for Windows?

    I'm not sure how you managed to interpret what I wrote that way... or what I was responding to...

    The post I am responding to right now is the only one in the quote tree that doesn't make sense.


  • @Bulb said:

    @Arnavion said:
    • On the subject of classes, there aren't any. There are structs. Remember how in C, you don't define member functions on a struct? Instead you define them outside and usually put the struct type as the first parameter? That's how it's done in Go too.

    I would actually say this is not a WTF at all. In fact more new languages do this, because it has plenty of advantages and no disadvantages.

    Please explain how this is better than using methods. How do you control access to members if there aren't any methods?



  • @morbiuswilters said:

    @Bulb said:
    @Arnavion said:
    • On the subject of classes, there aren't any. There are structs. Remember how in C, you don't define member functions on a struct? Instead you define them outside and usually put the struct type as the first parameter? That's how it's done in Go too.

    I would actually say this is not a WTF at all. In fact more new languages do this, because it has plenty of advantages and no disadvantages.

    Please explain how this is better than using methods. How do you control access to members if there aren't any methods?


    Except they are methods, they're just lexically outside of the data definition. example


  • Considered Harmful

    The pointer having its own separate set of methods from the classstruct makes no sense to me.



  • @Ben L. said:

    Except they are methods, they're just lexically outside of the data definition. example

    Okay, what do you think that example actually shows? How is access to members controlled here? I just see a function that gets the value of a public member. How is encapsulation being performed here?



  • @morbiuswilters said:

    @Ben L. said:
    Except they are methods, they're just lexically outside of the data definition. example

    Okay, what do you think that example actually shows? How is access to members controlled here? I just see a function that gets the value of a public member. How is encapsulation being performed here?

    There's like, no encapsulation because, there's like, no class hierarchy, man... we're all equals here. 



  • @joe.edwards said:

    The pointer having its own separate set of methods from the classstruct makes no sense to me.

    From my understanding of what's been said in this thread, *T isn't "pointer-to-a-T", it's its own type completely. It may be a pointer to a T (I guess that's supposed to be the "correct" way to do it) but Go apparently lets you define pointer types with their own names that can point to some other type completely.



  • @morbiuswilters said:

    @Ben L. said:
    Except they are methods, they're just lexically outside of the data definition. example

    Okay, what do you think that example actually shows? How is access to members controlled here? I just see a function that gets the value of a public member. How is encapsulation being performed here?

    What? Since when is a lowercase type's lowercase field public?



  • @Ben L. said:

    Since when is a lowercase type's lowercase field public?

    Fucking hell.

    Follow-up question: if the "methods" aren't bound to the objects themselves, and there's no overloading of methods, then how do you keep from having a bunch of conflicting function names in the global namespace?



  • @Ben L. said:

    The post I am responding to right now is the only one in the quote tree that doesn't make sense.

    ...

    @Medinoc said:

    Can you compile on Windows for Windows, using MinGW or MSYS? If no however, and if you can't compile on MSVC either, I guess it means the only way to compile for Windows is cross-compiling from Linux. In which case, that's a WTF indeed.

    @Arnavion said:

    I only have MSVC installed, but AFAICT go doesn't spawn any cl.exe processes. I'm not sure how it actually compiles, but it does. Regardless of how it does it, you can indeed compile for Windows on Windows.

    In case you still don't get it:-

    Medinoc: Is it totally impossible to compile Go programs on Windows for Windows? Is cross-compiling on Linux the only way to compile Go programs for Windows? Or can it be done on Windows for Windows using MinGW or MSYS?

    Arnavion: I can be sure MinGW and MSYS are not required to compile on Windows for Windows, since I don't have them installed. I do have MSVC installed, but I have reason to believe Go didn't use it to compile on Windows for Windows either. But obviously I did manage to get a compiled binary of my program on Windows somehow, using Go on Windows. Thus, while I'm not sure how Go compiled my program on Windows for Windows (presumably using some built-in compiler?), I at least have an answer for your question about whether it is possible to compile Go programs on Windows for Windows. The answer is yes.



  • @morbiuswilters said:

    @joe.edwards said:
    The pointer having its own separate set of methods from the classstruct makes no sense to me.
    From my understanding of what's been said in this thread, *T isn't "pointer-to-a-T", it's its own type completely. It may be a pointer to a T (I guess that's supposed to be the "correct" way to do it) but Go apparently lets you define pointer types with their own names that can point to some other type completely.
    No. T* is still a pointer to T. However, you have the freedom to also say that T* implements the Sheep interface, and thus has a Baa() method. You can do this even if your T implements the Cow interface with a Moo() method.

    In other words, it's kinda like this not-really-C++ code:

    
    class T : Cow {
        void Moo();
    }
    
    class T* : Sheep { // Not valid C++
        void Baa();
    }
    
    void Func(T* animal) {
        (*animal).Moo(); // *animal is a T which is a Cow
        animal.Baa(); // animal is a T* which is a Sheep
    
        (*animal).Baa(); // Compile error. *animal is a T which isn't a Sheep.
        animal.Moo(); // Compile error. animal is a T* which isn't a Cow.
    }
    
    void main() {
        T pet;
        
        Func(&pet);
    }
    


  • @morbiuswilters said:

    @Ben L. said:
    Since when is a lowercase type's lowercase field public?

    Fucking hell.

    Follow-up question: if the "methods" aren't bound to the objects themselves, and there's no overloading of methods, then how do you keep from having a bunch of conflicting function names in the global namespace?

    What? Seriously, you have to stop listening to those guys. They're a bad influence on your brain.

    Methods are defined on types. Functions are defined on packages. This example has two methods (A.Foo and B.Foo) and two functions (Foo and main), all in the main package.


Log in to reply