Beefy Boxes and Bandwidth Generously Provided by pair Networks
Clear questions and runnable code
get the best and fastest answer
 
PerlMonks  

Does Go steal from Perl? :-)

by reisinge (Hermit)
on Aug 03, 2018 at 08:28 UTC ( [id://1219775]=perlmeditation: print w/replies, xml ) Need Help??

I've started to read The Go Programming Language. I have come across this example code:

// Dup1 prints the text of each line that appears more than // once in the standard input, preceded by its count. package main import ( "bufio" "fmt" "os" ) func main() { counts := make(map[string]int) input := bufio.NewScanner(os.Stdin) for input.Scan() { counts[input.Text()]++ } // NOTE: ignoring potential errors from input.Err() for line, n := range counts { if n > 1 { fmt.Printf("%d\t%s\n", n, line) } } }

The counts[input.Text()]++ construct looks pretty familiar, like a hash autovivification in Perl. Was this idea taken from Perl and put into Go?

Leave no stone unturned. -- Euripides

Replies are listed 'Best First'.
Re: Does Go steal from Perl? :-)
by tobyink (Canon) on Aug 03, 2018 at 09:23 UTC

    Autovivification in Perl happens when you try to dereference an undefined value as an arrayref or hashref. A new array or hash is silently created and the undefined value is changed to be a reference to that array or hash.

    There's no autovivification going on in the example you gave. There's only one hash used (though Go calls them maps, not hashes), and it's explicitly declared in the first line of the function.

    The counts[input.Text()]++ part relies on:

    • Go doesn't require you to predeclare what keys you'll use in a map. (Pretty common. Most languages that provide a hash/dictionary/map-like structure don't require you to predeclare what keys you'll use.)

    • If you read a map using a key which hasn't been assigned to yet, you get null/undefined. (Again very common.)

    • The language treats undefined values as zero when used in numeric contexts such as with the increment operator. (Almost universal in weakly typed languages.)

      There are some dangerous misconceptions in this analysis.

      Go is a strongly and explicitly typed language. There is no such thing as an undefined value and there is certainly no such thing as a numeric context. Instead, every variable has a well-defined, fixed type, specified (or inferred) when the variable is declared, and every type has a well-defined zero value, that is, any variable not explicitly assigned to will be initialized to the zero value of its type.

      Let's look at the first line then:

      • It declares a map, specifying explicitly that the keys are strings (which is a separate, pre-defined type in Go) and the values are integers. Using anything else than strings as keys or treating the values as anything other than integers would result in a compile time error.
      • It initializes the map, allocating the necessary internal data structures and returning the result. The zero value of any map type is such an empty, ready-to-use map.
      • It assigns this empty, initialized map to counts, using the := operator which declares a variable using type inference. The compiler knows that counts has to be a map[string]int, so you don't have to write it twice.

      In turn, the counts[input.Text()]++ line can be analyzed as follows:

      • Attempting to read the value for a key that does not have an entry in the map will result in the zero value of the value type, in this case 0. Not because there is any kind of numeric context anywhere, but because the map's value type is integer.
      • Elements can be added to the map with assignment operations.
      • x++ is not an expression in Go, and you can't use it as such; it is a statement that can be thought as the atomic equivalent of x=x+1, and as such, it is an assigment.

      It is likely that the authors of go recognized the code in question as a common idiom and wrote the language standard with it in mind, but the details explained above make it clear that the underlying concepts are rather different from those of Perl.

        There is no such thing as an undefined value and there is certainly no such thing as a numeric context

        Actually, even strongly typed languages have a numeric context - and other contexts. In strongly typed languages like Go, the context is used (at compile time) to enforce that a variable or value of a specific type is used.

        Also, being strongly typed does not automatically exclude the concept of "undefined value". "undefined" is a sentinel that is reserved and predefined by the language. Many "purists" mistakenly equate undefined with uninitialized. While uninitialized implies undefined, the reverse need not be true. Like "NaN" (not a number) is useful, having undefined as a reserved, predefined representation of a sentinel can also be useful. Otherwise, you have to decide on a value to use as a sentinel, then hope that value never appears in your data.*

        Unfortunately, the way Perl uses undefined contributes to the confusion between uninitialized and undefined. It would have been better for Perl to have a flag for uninitialized as well as undefined. At least Perl has a test for undefined. Off hand, I don't recall any other languages that provide a test for undefined.

        ---

        * While NaN could be used as a sentinel, it's mathematical definition is an unrepresentable value, which is different from an undefined value.

Re: Does Go steal from Perl? :-)
by Eily (Monsignor) on Aug 03, 2018 at 09:24 UTC

    All languages are built using concepts from others, perl is no exception (is takes a lot from C, shell languages, awk...), so even if there was autovivification in Go, there wouldn't be much to say about it.

    But this is not autovivification for two reasons. First, autovivification is about nested structure. When you try to use a non existing element of a structure (array or hash) as a structure itself, perl may create that element for you. For example, if %hash is empty and you try to modify $hash{Clef}[0], you are trying to access the array at key Clef, so perl will create an array. In your Go example, it just shows that a non existing int value in a map is treated as 0.

    The second reason this is not autovivification, is because it is impossible to have autovivification (AFAICT) in this language. You'll notice that in perl, you know that $hash{Key} is an hash value because of { }. And you know that it contains an array because [0] acccesses an array element. Go, like Python, doesn't have such a distinction, thing[value] could be an array item, a map (dict, or hash) access, or access to a char in a string. In those languages, it's the operand that defines which operation to use, while in perl it's the operator that defines what the operand is. This means that in python* if you try to access thing[value][otherValue] and the first level doesn't exist, python has no idea if you were trying to access a string, an array, a dict or something else, so it can't create it for you.

    *I know python better than Go, but the idea still applies

      Go, like Python, doesn't have such a distinction, thingvalue could be an array item, a map (dict, or hash) access, or access to a char in a string. In those languages, it's the operand that defines which operation to use

      Again, no.

      Go is a strongly typed language, every variable has a well-defined type associated it. You can have a nested map type c := map[string]map[string]int, in which case you (and the compiler) can know that its element c["key"] is of type map[string]int and the sub-elements of that (c["key"]["subkey"]) are of type int. Assigning a value of any other type to those keys would result in a compile time error.

      So in theory, you could have autovivification in the language - but in practice you can't, you have to explicitly initialize the mid-level maps yourself, as explained by this blog post: Go maps in action.

      The underlying reason for this, I think, is that Go differentiates between a nil map and an empty map. The zero value of a(n uninitialized) map is a nil map, you have to initialize yourself with make. A nil map can be safely read from, but assigning any key to it results in a runtime panic. When you declare a nested map as the example above, the mid-level maps will be nil maps, and as such, are not safe to assign to. (I consider this to be a design wart of the language.)

        Thanks for the clarification, it was pretty unwise of me to say so much without checking my facts more. So while Go can't have the full power of autovivification on undeclared structures, it would be conceivable when declared as nested data but undefined. It's just not the case. Not sure I'm going to put that knowledge to good use any time soon, but at least I relearned not to assume what a language dis or is like :-)

Re: Does Go steal from Perl? :-)
by afoken (Chancellor) on Aug 06, 2018 at 18:17 UTC
    The counts[input.Text()]++ construct looks pretty familiar, like a hash autovivification in Perl. Was this idea taken from Perl and put into Go?

    I've used that trick in MUMPS, too. And I would be quite surprised to be the first one using it in MUMPS, given its decades of history. I did not find that trick in code by the interpreter's manufacturer (Micronetics), and neither did I find it in code written by my ex-coworkers. But yes, I stole the trick from Perl. In fact, my MUMPS code looked a lot like Perl, but with a different kind of "line noise".

    Alexander

    --
    Today I will gladly share my knowledge and experience, for there are no sweeter words than "I told you so". ;-)

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlmeditation [id://1219775]
Approved by haukex
Front-paged by Corion
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others sharing their wisdom with the Monastery: (2)
As of 2024-03-19 04:08 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found