Gotchas

From Rosetta Code
Gotchas is a draft programming task. It is not yet considered ready to be promoted as a complete task, for reasons that should be found in its talk page.
Definition

In programming, a gotcha is a valid construct in a system, program or programming language that works as documented but is counter-intuitive and almost invites mistakes because it is both easy to invoke and unexpected or unreasonable in its outcome.

Task

Give an example or examples of common gotchas in your programming language and what, if anything, can be done to defend against it or them without using special tools.

J

Issues with array rank and type should perhaps be classified as gotchas. J's display forms are not serialized forms and thus different results can look the same.

<lang J> ex1=: 1 2 3 4 5

  ex2=: '1 2 3 4 5'
  ex1

1 2 3 4 5

  ex2

1 2 3 4 5

  10+ex1

11 12 13 14 15

  10+ex2

|domain error</lang>

Also, constant values with a single element are "rank 0" arrays (they have zero dimensions) while constant values with some other count of elements are "rank 1" arrays (they have one dimension -- the count of their elements -- they are lists).

Thus, 'a' is type character, while 'abc' is type list of characters (or type list of 3 characters, depending on how you view type systems). This can lead to surprises for people who are inexperienced with the language and who are working from example (todo: list some examples of this).

Another gotcha with J has to do with function composition and J's concept of "rank". Many operations, such as + are defined on individual numbers and J automatically maps these over larger collections of numbers. And, this is significant when composing functions. So, a variety of J's function composition operators come in pairs. One member of the pair composes at the rank of the initial function, the other member of the pair composes at the rank of the entire collection. Picking the wrong compose operation can be confusing for beginners.

For example:<lang J> 1 2 3 + 4 5 6 5 7 9

  +/ 1 2 3 + 4 5 6

21

  1 2 3 +/@:+ 4 5 6

21

  1 2 3 +/@+ 4 5 6

5 7 9</lang>

Here, we are adding to lists and then (after the first sentence) summing the result. But as you can see in the last sentence, summing the individual numbers by themselves doesn't accomplish anything useful.

Raku

Raku embraces the philosophy of DWIM or "Do What I Mean". A DWIMmy language tends to avoid lots of boilerplate code, and accept a certain amount of imprecision to return the result that, for the vast majority of the time, is what the user intended.

For example, a numeric string may be treated as either a numeric value or a string literal, without needing to explicitly coerce it to one or the other. This makes for easy translation of ideas into code, and is generally a good thing. HOWEVER, it doesn't always work out. Sometimes it leads to unexpected behavior, commonly referred to as WAT.

It is something of a running joke in the Raku community that "For every DWIM, there is an equal and opposite WAT".

Larry Wall, the author and designer of Perl and lead designer of Raku, coined a term to describe this DWIM / WAT continuum. The Waterbed Theory of Computational Complexity.

The Waterbed theory is the observation that complicated systems contain a minimum amount of complexity, and that attempting to "push down" the complexity of such a system in one place will invariably cause complexity to "pop up" elsewhere.

Much like how in a waterbed mattress, it is possible to push down the mattress in one place, but the displaced water will always cause the mattress to rise elsewhere, because water does not compress. It is impossible to push down the waterbed everywhere at once.

There is a whole chapter in the Raku documentation about "Traps to Avoid" when beginning in Raku, most of which, at least partially are due to WATs arising from DWIMmy behavior someplace else.

One such DWIMmy construct, which can trip up even experienced Raku programmers is The Single Argument Rule.

Single argument is a special exception to parameterization rules causing an iterable object to be be automatically flattened when passed to an iterator if only a single object is passed.


E.G. Say we have two small list objects; (1,2,3) and (4,5,6), and we want to print the contents to the console.

<lang perl6>.say for (1,2,3), (4,5,6);</lang>

(1,2,3)
(4,5,6)

However, if we only pass a single list to the iterator (for), it will flatten the object due to the single argument rule.

<lang perl6>.say for (1,2,3);</lang>

1
2
3

If we want the multiple object non-flattening behavior, we need to "fake it out" by adding a trailing comma to signal to the compiler that this should be treated like multiple object parameters even if there is only one. (Note the trailing comma after the list.)

<lang perl6>.say for (1,2,3),;</lang>

(1,2,3)

Conversely, if we want the flattening behavior when passing multiple objects, we need to manually, explicitly flatten the objects.

<lang perl6>.say for flat (1,2,3), (4,5,6);</lang>

1
2
3
4
5
6

Single argument mostly arose in Raku to make it act more like Perl 5, for which it was originally conceived of as a replacement. Perl 5 flattens collective object parameters by default, and the original non-flattening behavior was extremely confusing to early Perl / Raku crossover programmers. Larry Wall came up with single argument to reduce confusion and increase DWIMiness, and it has overall, but it still leads to the occasional WAT when programmers first bump up against it.


Wren

There are 3 gotchas in Wren which immediately spring to mind because they've bitten me more than once in the past.

1. The classic 'if (a = b) code' problem which everyone who's familiar with C/C++ will know about and which is already adequately described in the linked Wikipedia article together with the standard remedy.

2. In Wren, class fields (unlike variables) are never declared but instead their existence is deduced from usage which can be in any method of the class. This is possible because fields (which are always private to the class) are prefixed with a single underscore if instance or a double underscore if static, and no other identifiers are allowed to begin with an underscore. Fields are always assigned the value 'null' unless they are assigned to immediately.

Normally, this works fine but the problem is that if you mis-spell the field name, then it won't be picked up by the compiler which will simply allocate a slot for the mis-spelled field within the class's fields symbol table. You may therefore end up assigning to or referencing a field which has been created by mistake!

The only defence against this gotcha is to try and keep field names short which reduces the chance of mis-spelling them.

3. Wren's compiler is single pass and if it comes across a method call within a class which has not been previously defined it assumes that it will be defined latter. Consequently, if you forget to define this method later or have already defined it but then mis-spelled or misused it, the compiler won't alert you to this and at some point a runtime error will arise.

The same defence as in 2. above can be used to defend against this gotcha though, if the methods are public (and hence need self-explanatory names), it may not be practical to keep them short.

I've tried to construct an example below which illustrates these pitfalls. <lang ecmascript>class Rectangle {

   construct new(width, height) {
       // Create two fields.
       _width = width
       _height = height
   }
   area {
      // Here we mis-spell _width.
      return _widht * _height
   }
   isSquare {
       // We inadvertently use '=' rather than '=='.
       // This sets _width to _height and will always return true
       // because any number (even 0) is considered 'truthy' in Wren.
       if (_width = _height) return true
       return false
   }
   diagonal {
       // We use 'sqrt' instead of the Math.sqrt method.
       // The compiler thinks this is an instance method of Rectangle
       // which will be defined later.
       return sqrt(_width * _width + _height * _height)
   }

}

var rect = Rectangle.new(80, 100) System.print(rect.isSquare) // returns true which it isn't! System.print(rect.area) // runtime error: Null does not implement *(_) System.print(rect.diagonal) // runtime error (if previous line commented out)

                           // Rectangle does not implement 'sqrt(_)'</lang>