A lot of these are simply the Go people declaring certain conventions by fiat: gofmt, the go build tool, a large standard library, and the like.
These are a lot of the benefits that Python had: everybody pretty much has to do things the same way (and there's PEP 8 that establishes non-enforced conventions), so I can start playing with a new codebase without having to figure out their conventions and build system too.
It's unbelievable how helpful it is. The Go article that was posted last week was lambasted for a lot of good reasons, but even as a Go fan I was annoyed at how hard it was for me to quickly scan the source because it didn't follow the conventions that I'm used to staring at quickly.
Go seems like a rehash of Ada, without the option to dynamically linked. Maybe an improved version of Ada.
I liked some feature of Ada a lot, it was safe, input, output, in/out parameters for functions. Of course to make itself useful as a lower level language you need to be able to call OS functions, which in unix means C. So then you have to start wrapping... Some parts of Ada were terrible (string handling comes to mind.)
I think its worth a look, but I'm still not sure what problem its trying to solve.
You posted this a few days ago, I didn't understand it and still don't understand after another read through. Can you give either a dumbed down summary or a scenario or an example of a language that doesn't do this?
the last time he posted this was in response to me, so I'll join the discussion here to avoid duplication. This was his response when I asked for some clarification:
>My problem with Go is that it does not have sum-types and pattern-matching. This means that branching (conditionals) in Go do not gain any type-information. And that means that programmers have to manually keep track of the invariants that hold true in each of their conditionals, and if they get them wrong, they get no help from the compiler.
Basically, the idea is that when you branch on a conditional, information is gained. This information may be represented in the type system and used by the compiler to verify safety, or it can be ignored. If it is ignored, the language is said to have "boolean blindness".
Example:
if (ptr == NULL) {
... a ...
} else {
... b ...
}
In branch a and branch b, different invariants about ptr hold. But the language/compiler are not verifying any of these invariants.
Instead, consider:
data Maybe a = Nothing | Just a
This defines a type "Maybe", parameterized by a type variable "a", and it defines two "data constructors" that can make a Maybe value: "Nothing" which contains nothing, and "Just" which contains a value of type "a" inside it.
This is known as a "sum" type, because the possible values of the type are the sum of all data constructor values.
We could still use this sum data-type in a boolean-blind way:
if isJust mx then
.. use fromJust mx .. -- UNSAFE!
else
.. can't use content of mx ..
However, using pattern-matching, we can use it in a safe way. Assume "mx" is of type "Maybe a":
case mx of
Nothing -> ... There is no value of type "a" in our scope
Just x -> ... "x" of type "a" is now available in our scope!
So when we branch on the two possible cases for the "mx" type, we gain new type information that gets put into our scope.
"Maybe" is of course a simple example, but this is also applicable for any sum type at all.
If your language does branching without giving you back any new type information, it means you have to manually track the invariants and conditionals your program is in. If you get it wrong, your program will die at runtime. Instead, the language can almost always provide you some kind of proof that you can pass along of the conditional's truthness, which makes it safe to rely on it.
I may be mistaken, but I believe type switches in Go address this criticism:
type Fruit struct {
Name string
Species string
}
// define a method on Fruit structs named Rename
func (f *Fruit) Rename(s string) {
f.Name = s
}
type Candy struct {
Name string
Company string
}
// define a method on Candy structs named Rename
func (c *Candy) Rename(s string) {
c.Name = s
}
// any type that defines a method named Rename is also
// of type Renamable.
type Renamable interface {
Rename(string)
}
// produce something that implements Renamable. Its
// fields will be unknown, but the method Rename will be
// known to exist.
func SomeProducer(...) Renamable {
...
}
x := SomeProducer(...)
// at this point, x is of type Renamable. We do not
// know what fields it has. The only thing we know is
// that it has some method named Rename that takes a
// single string for its only argument and returns
// nothing.
switch t := x.(type) {
case Fruit:
// T is of type Fruit, while x is of type Renamable
fmt.Print(t.Species)
case Candy:
// T is of type Candy, while x is of type Renamable
fmt.Print(t.Company)
default:
// some other type.
}
Not exactly. You see, you have to define new types from scratch with your approach. With Haskell's approach, you just have to use the Maybe parametric type, which just needs to be defined once. In other words, you just go and use a generic type instead of writing your own, like new List<Integer> instead of new ListInteger in Java.
unless I'm missing something, that seems to address a separate difference in the specifics of Go and Haskell's type systems, namely that Go's interfaces and Haskell's sum types are different. Yes, they're different. I really only meant to say that the article is predicated on the notion that we're only able to branch on booleans instead of predicates. As for checking for null values...
var x *MyType
...
switch x {
case nil:
// blah
default:
// blah
}
tada, no boolean required, no having to "establish the provenance" of any bits. Sure, inside the nil case, the compiler doesn't actually stop you from unsafely attempting to access the fields of x, and, in that way, Haskell is safer than Go.
I'm not trying to assert that Go is better than Haskell or that they are the same; I've never programmed Haskell so I'm not qualified to make such a statement. I'm merely addressing the specific "Boolean Blindness" criticism.
I don't really think that criticism is in any way valid, since "Boolean Blindness" isn't a criticism of a language design, it's a criticism of how one might use a particular language. To say that Go committed "Boolean Blindness" is predicated on the following assumptions:
- you can only branch on a boolean
- given a value and a type, the only thing you can do is obtain a boolean indicating whether or not that value is of that type
as far as Go is concerned, those statements are factually inaccurate.
Given some variable `x` of unknown type, and a type `t`, is there some way, in Haskell, to create a boolean `v` such that the value of `v` is `true` when `x` is of type `t` and `false` otherwise? And if so, is there some way to branch on this boolean value `v`? Because if that is true, then how has Haskell not committed the same atrocity of "Boolean Blindness"?
> tada, no boolean required, no having to "establish the provenance" of any bits. Sure, inside the nil case, the compiler doesn't actually stop you from unsafely attempting to access the fields of x,
But you do have to establish the provenance of your conditional. You have to keep track of whether you compared against nil yourself.
Whether this is expressed as if(x != nil) or as a boolean-blind switch case, the information is lost and it is up to the user to keep track of the meaning of 'x' under all the conditions it is run.
Of course Haskell is capable of expressing boolean blind and unsafe code. But Haskell, unlike go is also capable of expressing non-blind, safe code.
Go is only able to express boolean blind code (again, it is not about a boolean condition but about whether branching buys you type information). When you compare against nil, it is up to you to keep track of the provenance.
it is up to the user to keep track of the meaning of 'x' under all the conditions it is run
OK, so it's a bit like you're chaining assumptions together, rather than putting them next to one another? This makes Monads, esp. `>>=`, make a bunch more sense to me.
Like, the difference is between chaining/linking two operations (incl. checking what `x` is), by using the result of one check as the input to the other, and basically putting two operations next to one another (not to say that the `if` is meaningless, just that once the `if` is evaluated, the associated block has to take the `if`'s word for it).
The bonus is that Haskell handles the first part, the check, for you if you let it. It's an implicit (well sorta) and immutable precondition to whatever's next, at a logical/linguistic level.
And I guess the reason why booleans in particular are problematic is because you're sorta bolting them on, and they inherently compress information into yes/no, which is otherwise not terribly useful for the block following?
> You have to keep track of whether you compared against nil yourself.
that's true in the case of comparing against nil, which I've already agreed with. I never said that Go is as safe as or safer than Haskell, I said that your assertion that there's "no way to branch and get type information" is a factual inaccuracy that misrepresents the language.
The fact that you haven't commented on type switches in any way shows that you're not really interested in anything other than your own sense of superiority.
Haskell is a better programming language. As a programmer of a programming language that is not Haskell, I am inferior to you.
It's not just comparing against nil, it's any comparison or switch-case besides a type-switch.
A type-switch seems to be non-boolean-blind indeed, but it seems to have too much syntactic overhead, as Go code generally uses if branches when a type-safe case is due (e.g: When analyzing return codes).
yeah, it's pretty ugly. I don't think anyone in the Go community would disagree with you there.
>Go code generally uses if branches when a type-safe case is due (e.g: When analyzing return codes).
Sure, that's mostly true of that use case. The difference is that if you return multiple values, one of which is an error, the other returned value is often the zero-value for that type. If you have an error, the other value isn't de facto nothing. I have to admit that I've never used that "feature" and I can't even contrive a hypothetical situation where I would want to.
The reality is that it's not a sin to branch on a value. If you want to do something based on whether one date is earlier than the other, I can't see why you would need to bring a type system to a boolean party.
I think Rob Pike summed it up really well, when he said something to the effect of "people ask me why we would make a language that's not theoretically interesting, and they're right; Go isn't very interesting from a theoretical standpoint, it's just useful". They're not really setting out to revolutionize programming theory; they're setting out to revolutionize programming practice. Go is designed to let large numbers of non-genius-level programmers make concurrent software that's pretty stable, while Haskell is designed to scare the everliving shit out of anyone that sucks at programming. From my experiences, the Haskell programmers that I know are probably the smartest programmers I know that have never made anything that's of any interest to non-programmers. That's not really meant to say something about the value of that type of programming; I think it's valuable, it's just not the type of programming that I'm interested in doing. A friend of mine that is a Haskell programmer once showed me how great of a programming language Coq is by showing me how to prove that multiplication is commutative, which is probably the most intellectually masturbatory thing I've ever seen anyone do with programming. Of course, some of my other friends who were into that type of programming thought it was very, very cool. By contrast, I made some toy that I showed to my infant niece and it made her giggle and that made me happy. The happiness of my users is more important to me than the purity of my code. So... it just depends what you want to do.
I think you're actually correct, and I appreciate what I've learned about Haskell's type system, but I think you're just missing the point of the Go project or what makes people like it. They haven't set out to make the most perfectly safe, beautiful and pure programming language, because Haskell already did that and the world doesn't need two Haskells; we just need a saner Java for writing big concurrent programs that can quickly compiled to machine code.
if x == nil {
x.doSomethingCrazy(); // Runtime error?
} else {
...
}
"Boolean blindness" (bah) is that either the type system should catch this at compile time (because we know x is nil), or the language should make it impossible to phrase (pattern matching to pull the information out, such as with the Maybe type).
Go has many type system oddities (reflection instead of generics, for instance), but the things they _have_ done (auto-implementation of interfaces, for example) are pretty cool.
What benefit does it have? What purpose does it serve?
As far as I know, these guys have a reputation in industry moreso than in academic circles. It seems entirely believable that they have taken no interest in language design outside of industry, where the state of the art lags a few decades behind that of academia.
The benefit is not having to code it. The most bug-free components are the ones that aren't there. Also, it's only v1. I don't think anybody's said that they're opposed to putting it in, so give it time. Maybe file a feature request.
I think if they add sum types and pattern matching or otherwise make this feature possible, it will completely change the way people write Go code and it would become a very different language.
Consider all the error-checks in Go -- these are all incorrect from a boolean-blindness perspective, and that would be a lot of code to rewrite if they add proper constructs to Go. Thus, I really believe it is just a mistake -- one that is only possible to make out of ignorance.
Because Go is concurrent and has mutable types, race conditions could cause the assumptions gleaned from the booleans to be false in the very next statement. Boolean blindness is a result of the language.
No, that's false -- consider the Maybe case. If you carry around the content of the Maybe, it will still remain valid even after the Maybe itself is overwritten. Races are of course still possible but they don't relate to boolean blindness.
I guess I don't understand the difference between have a typed Go pointer and asking if it is null or not. Nil is nothing, a non-nil value means you can use it. I feel like I'm probably missing a piece of this and I feel dumb about it, sorr I'm not groking it.
Every time you dereference a pointer in any language with a null pointer type, you're implicitly admitting the chance of a failure (panic, exception, ...). With a sum type (like Maybe or option in ML), you never have this possibility; you have to handle the two cases (null and not-null) separately, and the compiler checks to ensure that you can never use the pointer unless you're in the not-null branch. What Bob Harper is talking about is that, in for example Go, you can write this:
if x == nil {
... use x ...
}
Which is of course a bug. What you meant to write is:
if x != nil {
... use x ...
}
But the Go compiler cannot check this in the general case. However, in ML or Haskell, it's easy. This does not compile, because the type of x will be option<T> instead of T:
match x with
| None -> ... (* use x *)
| Some _ -> () (* do nothing *)
But this does compile:
match x with
| None -> () (* do nothing *)
| Some y -> ... (* use y *)
Because the type of y will be T instead of option<T>.
I understand. I guess I don't do even FP to find that to be terribly important to me. To me, and I understand it's not, the pattern matching against None looks to me like a nil check in Go. Also, with your example, the compiler could definitely check, for thread-bound variables, that that code is "incorrect".
In the general case, the compiler can't check to see that that code is incorrect. Consider the case in which the boolean result "x == nil" is threaded around from function to function, aliased (you handed out a pointer to it), etc... Also consider the case in which "x" is aliased (you handed out a pointer to it) and it gets mutated to nil in the middle of the branch by calling some function.
If you go down this road, you end up making the compiler perform a very difficult (probably undecidable) proof. There has been lots of research on this and it's not easy...
Sorry, this may be becoming off-topic, and to be honest, I'd considered multithreading (especially given Go's strong suite). Is that just not something Haskell or equivalent can do then?
Disclaimer: I love Go and the more I learn about Haskell the more I love it, as well.
So is the distinction here that, basically, you push the distinction between a "nothing" and a "something" down into the language layer? IOW, the `if isJust mx` is the programmer trying to reason about a type, whereas `case mx of` is delegating that reasoning to the compiler/language?
In general I think that's a good principle, but at least in this example I don't quite grok why `if isJust mx` is out-and-out unsafe. Maybe this is just because the (heh) Maybe example is too simple to illustrate the unsafeness.
It does seem more error prone in practice, in terms of engineering & maintenance (read: more likely to lead to bugs). `isJust mx` means you are methodically separating out the verification of what type it is from the access and usage of that type, and since this is all happening at the program layer, you've got to keep the assumptions in your `if` consistent with the assumptions of your type. Conversely the `case of mx` statement leverages the type system to do the work for you.
I'll try to put it another way: the code inside your `if isJust` clause is not semantically related to the clause itself except in the most superficial way. However, with `case mx of`, those clauses are inextricably related since you've (again) pushed the semantics of checking the type and unboxing it down a layer, into Haskell.
I may have been slightly misleading if I implied "isJust" is unsafe. It is perfectly safe. What's unsafe is "fromJust" which crashes at runtime if the Maybe value is in fact a Nothing. "isJust" is code-smell because it means you're likely to need a "fromJust" later.
The problem is: What if the condition is changed later? What if you use "fromJust" in the else branch? What if the code is moved around outside of the condition's scope?
The above is meant to illustrate that the "isJust/fromJust" anti-pattern is out-and-out unsafe. You get no compile-time assurances. If you avoid "fromJust" altogether (and equivalent constructs, of course), you have a 100% guarantee that you will not crash from expecting a Just where there's a Nothing (IOW, nil dereference).
Additionally, "Just" and "Nothing" are data constructors (can be viewed as value tags) within the Maybe type, they are not "types" themselves.
Except for these points, I think you are generally describing it correctly.
This kind of thing (avoiding boolean blindness) as well as a few more safety nets in Haskell is the reason lots of Haskell code is said to Just work once it compiles.
OK, yes, I can see how `fromJust`, more specifically, is the unsafe part. You're dereferencing a pointer which is probably non-nil but the compiler can't tell you because you've insisted on checking it manually rather than communicating your intent to the compiler more directly.
So when you say "avoid fromJust," the alternative in practice is to use pattern matching, yes? The example you used above was a case statement. A guard clause is another obvious one, as are additional clauses (?) in a function.
Sorry, but having had some dealings with Ada many years ago I'm sure Go is little like it. Go has CSP in its ancestry, what with channels and goroutines, and both are fundamental to it whereas Ada has nothing similar?
Ada's tasks aren't defined to be greenlets; an implementation can make then OS threads or processes, limiting their number. And rendezvousing is more cumbersome than channels, e.g. passing a channel over a channel.
You could be right about the spec. I think gccgo no longer does one OS thread per goroutine as of Go 1.0 but I've been unable to find a clear definition to point to.
There are some neat things about it. Being able to retroactively distill an interface from code you do not have the source to. Sometimes I wish I could do this in c# when I mock stuff up.
Goroutines and channels are cool because they are simple to use. We have tasks in c# and I could construct channels in countless ways. But go makes them dirt simple.
I don't like the terse c-like naming conventions but that is a manner of taste. Makes it feel more close to the metal and less enterprisey I guess.
I definitely agree with your comment about interfaces. This appears to be a concept that could be transplanted to C++, and would help quite a bit. Often in C++, people rely on template (compile-time polymorphism) because it is not possible to retroactively use interfaces (run-time polymorphism).
Yeah I'm guessing that is why they went that direction. It really isn't any less enterprisey than anything else and we ended up with stuff like fmt.printf, who would have guessed printf is in something called fmt. Well I guess it stands for format, but is printf primarily a formatting function? It prints and the formatting is a convenience. If we are meant to use it for formatting, it is badly named.
I'm learning a bit of go at the moment, and liking it a lot. One thing which caused me a little head scratching was doing something like this:
for _, item := range slice_of_obj {
item.update()
}
where the 'update' method wrote to some member data in item.
It seems that the 'item' is a copy of, rather than an alias to, the underlying object, so the mutating 'update' call is updating the copy, which is then discarded. (The 'right way' to do this to use the loop index and slice_of_obj[i], but that seems mildly barbaric)
This seemed odd to me, particularly since no error was generated. Can anyone comment if I'm doing anything particularly dumb here? If Go had a concept of 'const', at least it could throw an error when you mutate the copy which is going to be discarded.
Btw code that uses range will always be faster than alternative with for loop and indexing (obj[i]) as range-based code can be optimized by Go compiler not to perform any bound checks.
Yes, that works nicely, thanks. But sometimes (rarely) you do want a contiguous bunch of objects, rather than have the indirection.
I think ideally, it would work (as an alias) but if not, I think it should error. Or maybe this is a FAQ, with a good rationale, which I just haven't got to yet.
The effect you are seeing there is because the as it says in the documentation: "The iteration values are assigned to the respective iteration variables as in an assignment statement." So item is being assigned and because it's an obj and not a *obj you are getting a copy.
Whereas slice_of_obj[i] is accessing the element within the slice without a copy. One solution to this problem would be to store pointers in your slice instead of objects.
57 comments
[ 3.8 ms ] story [ 119 ms ] threadThese are a lot of the benefits that Python had: everybody pretty much has to do things the same way (and there's PEP 8 that establishes non-enforced conventions), so I can start playing with a new codebase without having to figure out their conventions and build system too.
I liked some feature of Ada a lot, it was safe, input, output, in/out parameters for functions. Of course to make itself useful as a lower level language you need to be able to call OS functions, which in unix means C. So then you have to start wrapping... Some parts of Ada were terrible (string handling comes to mind.)
I think its worth a look, but I'm still not sure what problem its trying to solve.
[1] http://existentialtype.wordpress.com/2011/03/15/boolean-blin...
>My problem with Go is that it does not have sum-types and pattern-matching. This means that branching (conditionals) in Go do not gain any type-information. And that means that programmers have to manually keep track of the invariants that hold true in each of their conditionals, and if they get them wrong, they get no help from the compiler.
Example:
In branch a and branch b, different invariants about ptr hold. But the language/compiler are not verifying any of these invariants.Instead, consider:
This defines a type "Maybe", parameterized by a type variable "a", and it defines two "data constructors" that can make a Maybe value: "Nothing" which contains nothing, and "Just" which contains a value of type "a" inside it.This is known as a "sum" type, because the possible values of the type are the sum of all data constructor values.
We could still use this sum data-type in a boolean-blind way:
However, using pattern-matching, we can use it in a safe way. Assume "mx" is of type "Maybe a": So when we branch on the two possible cases for the "mx" type, we gain new type information that gets put into our scope."Maybe" is of course a simple example, but this is also applicable for any sum type at all.
If your language does branching without giving you back any new type information, it means you have to manually track the invariants and conditionals your program is in. If you get it wrong, your program will die at runtime. Instead, the language can almost always provide you some kind of proof that you can pass along of the conditional's truthness, which makes it safe to rely on it.
I'm not trying to assert that Go is better than Haskell or that they are the same; I've never programmed Haskell so I'm not qualified to make such a statement. I'm merely addressing the specific "Boolean Blindness" criticism.
I don't really think that criticism is in any way valid, since "Boolean Blindness" isn't a criticism of a language design, it's a criticism of how one might use a particular language. To say that Go committed "Boolean Blindness" is predicated on the following assumptions:
- you can only branch on a boolean
- given a value and a type, the only thing you can do is obtain a boolean indicating whether or not that value is of that type
as far as Go is concerned, those statements are factually inaccurate.
Given some variable `x` of unknown type, and a type `t`, is there some way, in Haskell, to create a boolean `v` such that the value of `v` is `true` when `x` is of type `t` and `false` otherwise? And if so, is there some way to branch on this boolean value `v`? Because if that is true, then how has Haskell not committed the same atrocity of "Boolean Blindness"?
But you do have to establish the provenance of your conditional. You have to keep track of whether you compared against nil yourself.
Whether this is expressed as if(x != nil) or as a boolean-blind switch case, the information is lost and it is up to the user to keep track of the meaning of 'x' under all the conditions it is run.
Of course Haskell is capable of expressing boolean blind and unsafe code. But Haskell, unlike go is also capable of expressing non-blind, safe code.
Go is only able to express boolean blind code (again, it is not about a boolean condition but about whether branching buys you type information). When you compare against nil, it is up to you to keep track of the provenance.
OK, so it's a bit like you're chaining assumptions together, rather than putting them next to one another? This makes Monads, esp. `>>=`, make a bunch more sense to me.
Like, the difference is between chaining/linking two operations (incl. checking what `x` is), by using the result of one check as the input to the other, and basically putting two operations next to one another (not to say that the `if` is meaningless, just that once the `if` is evaluated, the associated block has to take the `if`'s word for it).
The bonus is that Haskell handles the first part, the check, for you if you let it. It's an implicit (well sorta) and immutable precondition to whatever's next, at a logical/linguistic level.
And I guess the reason why booleans in particular are problematic is because you're sorta bolting them on, and they inherently compress information into yes/no, which is otherwise not terribly useful for the block following?
that's true in the case of comparing against nil, which I've already agreed with. I never said that Go is as safe as or safer than Haskell, I said that your assertion that there's "no way to branch and get type information" is a factual inaccuracy that misrepresents the language.
The fact that you haven't commented on type switches in any way shows that you're not really interested in anything other than your own sense of superiority.
Haskell is a better programming language. As a programmer of a programming language that is not Haskell, I am inferior to you.
There.
A type-switch seems to be non-boolean-blind indeed, but it seems to have too much syntactic overhead, as Go code generally uses if branches when a type-safe case is due (e.g: When analyzing return codes).
yeah, it's pretty ugly. I don't think anyone in the Go community would disagree with you there.
>Go code generally uses if branches when a type-safe case is due (e.g: When analyzing return codes).
Sure, that's mostly true of that use case. The difference is that if you return multiple values, one of which is an error, the other returned value is often the zero-value for that type. If you have an error, the other value isn't de facto nothing. I have to admit that I've never used that "feature" and I can't even contrive a hypothetical situation where I would want to.
The reality is that it's not a sin to branch on a value. If you want to do something based on whether one date is earlier than the other, I can't see why you would need to bring a type system to a boolean party.
I think Rob Pike summed it up really well, when he said something to the effect of "people ask me why we would make a language that's not theoretically interesting, and they're right; Go isn't very interesting from a theoretical standpoint, it's just useful". They're not really setting out to revolutionize programming theory; they're setting out to revolutionize programming practice. Go is designed to let large numbers of non-genius-level programmers make concurrent software that's pretty stable, while Haskell is designed to scare the everliving shit out of anyone that sucks at programming. From my experiences, the Haskell programmers that I know are probably the smartest programmers I know that have never made anything that's of any interest to non-programmers. That's not really meant to say something about the value of that type of programming; I think it's valuable, it's just not the type of programming that I'm interested in doing. A friend of mine that is a Haskell programmer once showed me how great of a programming language Coq is by showing me how to prove that multiplication is commutative, which is probably the most intellectually masturbatory thing I've ever seen anyone do with programming. Of course, some of my other friends who were into that type of programming thought it was very, very cool. By contrast, I made some toy that I showed to my infant niece and it made her giggle and that made me happy. The happiness of my users is more important to me than the purity of my code. So... it just depends what you want to do.
I think you're actually correct, and I appreciate what I've learned about Haskell's type system, but I think you're just missing the point of the Go project or what makes people like it. They haven't set out to make the most perfectly safe, beautiful and pure programming language, because Haskell already did that and the world doesn't need two Haskells; we just need a saner Java for writing big concurrent programs that can quickly compiled to machine code.
if x == nil { x.doSomethingCrazy(); // Runtime error? } else { ... }
"Boolean blindness" (bah) is that either the type system should catch this at compile time (because we know x is nil), or the language should make it impossible to phrase (pattern matching to pull the information out, such as with the Maybe type).
Go has many type system oddities (reflection instead of generics, for instance), but the things they _have_ done (auto-implementation of interfaces, for example) are pretty cool.
What's worse is that it appears this was all done out of ignorance, not conscious choice.
As far as I know, these guys have a reputation in industry moreso than in academic circles. It seems entirely believable that they have taken no interest in language design outside of industry, where the state of the art lags a few decades behind that of academia.
Consider all the error-checks in Go -- these are all incorrect from a boolean-blindness perspective, and that would be a lot of code to rewrite if they add proper constructs to Go. Thus, I really believe it is just a mistake -- one that is only possible to make out of ignorance.
If you go down this road, you end up making the compiler perform a very difficult (probably undecidable) proof. There has been lots of research on this and it's not easy...
So is the distinction here that, basically, you push the distinction between a "nothing" and a "something" down into the language layer? IOW, the `if isJust mx` is the programmer trying to reason about a type, whereas `case mx of` is delegating that reasoning to the compiler/language?
In general I think that's a good principle, but at least in this example I don't quite grok why `if isJust mx` is out-and-out unsafe. Maybe this is just because the (heh) Maybe example is too simple to illustrate the unsafeness.
It does seem more error prone in practice, in terms of engineering & maintenance (read: more likely to lead to bugs). `isJust mx` means you are methodically separating out the verification of what type it is from the access and usage of that type, and since this is all happening at the program layer, you've got to keep the assumptions in your `if` consistent with the assumptions of your type. Conversely the `case of mx` statement leverages the type system to do the work for you.
I'll try to put it another way: the code inside your `if isJust` clause is not semantically related to the clause itself except in the most superficial way. However, with `case mx of`, those clauses are inextricably related since you've (again) pushed the semantics of checking the type and unboxing it down a layer, into Haskell.
Am I anywhere close?
The problem is: What if the condition is changed later? What if you use "fromJust" in the else branch? What if the code is moved around outside of the condition's scope?
The above is meant to illustrate that the "isJust/fromJust" anti-pattern is out-and-out unsafe. You get no compile-time assurances. If you avoid "fromJust" altogether (and equivalent constructs, of course), you have a 100% guarantee that you will not crash from expecting a Just where there's a Nothing (IOW, nil dereference).
Additionally, "Just" and "Nothing" are data constructors (can be viewed as value tags) within the Maybe type, they are not "types" themselves.
Except for these points, I think you are generally describing it correctly.
This kind of thing (avoiding boolean blindness) as well as a few more safety nets in Haskell is the reason lots of Haskell code is said to Just work once it compiles.
So when you say "avoid fromJust," the alternative in practice is to use pattern matching, yes? The example you used above was a case statement. A guard clause is another obvious one, as are additional clauses (?) in a function.
Guard clauses in Haskell are actually boolean conditionals, and are boolean-blind.
Safe pattern-matching can be done via the function definition sugar:
Which is equivalent to a "case".The various functions that work with a Maybe value make it non-tedious to do things. For example, instead of:
You can write: Additionally, (>>=), fromMaybe and many other useful functions exist, and they're all based on safe pattern-matching.channels -> rendezvous
Google's implementation makes use of greenlets, while gccgo is using OS threads. Further implementations might decide on others approaches.
As for channels/rendezvous you might be correct, as I only played a bit with Ada.
Go has more in common with Oberon and Component Pascal than Ada
Goroutines and channels are cool because they are simple to use. We have tasks in c# and I could construct channels in countless ways. But go makes them dirt simple.
I don't like the terse c-like naming conventions but that is a manner of taste. Makes it feel more close to the metal and less enterprisey I guess.
I wish there was go#
Printf is all about "formatted printing". What you think the "f" at the end stands for and has stood for since C?
If anything, its the opposite of what you say, ie the "fmt" package name is not measleading but redundant!
It seems that the 'item' is a copy of, rather than an alias to, the underlying object, so the mutating 'update' call is updating the copy, which is then discarded. (The 'right way' to do this to use the loop index and slice_of_obj[i], but that seems mildly barbaric)
This seemed odd to me, particularly since no error was generated. Can anyone comment if I'm doing anything particularly dumb here? If Go had a concept of 'const', at least it could throw an error when you mutate the copy which is going to be discarded.
Btw code that uses range will always be faster than alternative with for loop and indexing (obj[i]) as range-based code can be optimized by Go compiler not to perform any bound checks.
I think ideally, it would work (as an alias) but if not, I think it should error. Or maybe this is a FAQ, with a good rationale, which I just haven't got to yet.
Whereas slice_of_obj[i] is accessing the element within the slice without a copy. One solution to this problem would be to store pointers in your slice instead of objects.
Some other languages (e.g. perl and I think also java) choose to make the loop var an alias, rather than a copy, to allow mutation:
will work as it reads.Is this always this way? I'd be interested in a discussion on the pros/cons of this. Some are obvious, but I'm interested in a deep discussion.