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.

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
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.

6502 Assembly

Numeric Literals

Integer literals used in instruction operands need to begin with a #, otherwise, the CPU considers them to be a pointer to memory. This applies to any integer representation, even ASCII. <lang 6502asm>LDA 'J' ;load the 8-bit value stored at memory address 0x004A into the accumulator. OR 3 ;bitwise OR the accumulator with the 8-bit value stored at memory address 0x0003


LDA #'7' ;load the ASCII code for the numeral 7 (which is 0x37) into the accumulator.</lang>

However, data blocks do not get the # treatment:

<lang 6502asm>byte $45 ;this is the literal constant value $45, not "the value stored at memory address 0x0045"</lang>

68000 Assembly

Numeric Literals

Integer literals used in instruction operands need to begin with a #, otherwise, the CPU considers them to be a pointer to memory. This applies to any integer representation, even ASCII. <lang 68000devpac>MOVE.L $12345678,D0 ;move the 32-bit value stored at memory address $12345678 into D0 MOVE.L #$12345678,D0 ;load the D0 register with the constant value $12345678</lang>

However, data blocks do not get the # treatment:

<lang 68000devpac>DC.B $45 ;this is the literal constant value $45, not "the value stored at memory address 0x0045"</lang>

LEA Does Not Dereference

When dereferencing a pointer, it is necessary to use parentheses. For these examples, <lang 68000devpac>MOVEA.L #$A04000,A0 ;load the address $A04000 into A0 MOVE.L A0,D0 ;move the quantity $A04000 into D0 MOVE.B (A0),D0 ;get the 8-bit value stored at memory address $A04000, and store it into the lowest byte of D0. MOVE.W (4,A0),D1 ;get the 16-bit value stored at memory address $A04004, and store it into the low word of D1.</lang>

However, the LEA instruction (load effective address) uses this parentheses syntax, but does not dereference! For extra weirdness, you don't put a # in front of a literal operand either.

<lang 68000devpac>LEA $A04000,A0 ;effectively MOVEA.L #$A04000,A0 LEA (4,A0),A0 ;effectively ADDA.L #4,A0</lang>

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.

Julia

There are several "gotchas" in Julia related to when and how a variable or object is considered constant versus non-constant. In Julia, a global object declared as `const` is constant:

const x = 2
x = 1 # will trigger a JIT Julia compiler error

However, arrays in Julia are mutable even if the variable name of the array is constant:

const a = [1, 2]
push(a, 4); a[2] = 0; # No error, and `a` is now [1, 0, 4]
a = [0, 0] # compiler error triggered by this, since we are assigning `a` itself not its mutable contents

If you want the contents of a list to be immutable, make a tuple instead of an array:

t = (1, 2, 3)  # now t[2] = 0 is flagged as an error

In Julia, a `struct` declared as a `struct` is immutable, yet can contain arrays that remain mutable even as part of an immutable struct:

struct S
    x::Int
    a::Vector{Int}
end

s = S(5, [3, 6])
s.x = 2 # ERROR
s.a[1] = 2 # Not an error!

but a `struct` declared as `mutable` is fully mutable:

mutable struct SM
    x::Int
    a::Vector{Int}
end

s = SM(5, [3, 6])
s.x = 2 # Not an error
s.a[1] = 2 # Not an error

In Julia, a non `const` variable declared in global scope (outside of a function) can be changed in global scope without any issue (although handling of global variables is done with extra bookkeeping and may be slow). However, such a variable can only be read inside a function. Attempts to change such a variable in a function result in an "undeclared variable" error unless the variable is declared within the function with the global keyword:

h = 5  # h is a global variable

function triangle(b)
    return  h * b / 2
end

triangle(10) # returns 25, no error

function changeh(b)
    h = b  # error here!
end

function change_declared_h(b)
    global h
    h = b  # no error here!
end

Perl

Perl has lists (which are data, and ephemeral) and arrays (which are data structures, and persistent), distinct entities but tending to be thought of as inter-changable. Combine this with the idea of context, which can be 'scalar' or 'list', and the results might not be as expected. Consider the handling of results from a subroutine, in a scalar context:

<lang perl>sub array1 { return @{ [ 1, 2, 3 ] } } sub list1 { return qw{ 1 2 3 } }

  1. both print '3', but why exactly?

say scalar array1(); say scalar list1();

sub array2 { return @{ [ 3, 2, 1 ] } } sub list2 { return qw{ 3 2 1 } }

say scalar array2(); # prints '3', number of elements in array say scalar list2(); # prints '1', last item in list</lang>

The behavior is documented, but does provide an evergreen topic for SO questions and blog posts.

Phix

Once I hear about a gotcha, I usually just fix it, so this might be a bit of a struggle...

There are however a few things to bear in mind for running under p2a/p2js (and sadly I can't "just fix JavaScript"):

In JavaScript, true!=1 and false!=0. Thankfully, there are very few places anyone ever actually compares bools against 0 and 1 using an infix operator, but occasionally you may need to use equal() and compare() instead, for true compatibility between desktop/Phix and p2js.

Likewise negative subscripts simply do not work in JavaScript Array destructuring, but you can however use $ and negative subscripts in non-destructuring operations.
[To be clear, I am specifically talking about working desktop/Phix code that gets transpiled to JavaScript, as opposed to making any claim about negative subscripts in hand-written JavaScript.]

One case that proved very difficult to track down was the statement tree[node+direction] = insertNode(tree[node+direction], key). As said elsewhere you should never attempt to modify the same thing in the same line twice. Breaking it into atom tnd = insertNode(tree[node+direction], key) and tree[node+direction] = tnd was needed to fix the issue.

See also Variable_declaration_reset - in particular the Phix and JavaScript entries.

Some fairly common minor mishaps:

Novice users often confuse a &= b with a = append(a,b):

  • When b is an atom (aka number) they mean the same thing.
  • When b is a sequence (or string) they are not the same:
  • a &= b can increase the length of a by any amount, including 0. [good for building strings]
  • a = append(a,b) always increases the length of a by 1. [usually bad/wrong for building strings]


Forward calls may thwart constant setup, eg:

forward procedure p()
p()
function f(object o) return o end function
constant hello = f("hello")
procedure p()
    ?hello  -- fatal error: hello has not been assigned a value
end procedure

Not a problem if the first executed statement in your program is a final main(), or more accurately not a problem after such a last statement has been reached.
Quite a few of the standard builtins avoid a similar issue, at the cost of things not officially being "constant" anymore, using a simple flag and setup routine.

Somewhat more tongue in cheek:

There is no difference between if a=b then and if a==b then, and neither modifies a.
It is not posible to compose a dangling else in Phix.
Phix has no macros. Or any that can make innocent-looking code "mean just about anything".
041 is the same number as 41, whereas 0o41 is the octal representation of 33 decimal.
"Hello" is not really the same as {'H','e','l','l','o'} but they are treated pretty much as if they are.
An expression such as s+1 may, with a suitable warning message, get auto-corrected to sq_add(s,1).
Block comments can be nested, a and b or c is illegal and demands extra parenthesis be used.
Zero minus one is always -1 instead of the traditionally expected 4,294,967,295.
Indexes are 1-based and s[0] triggers a run-time error.
(It surprises me to read Andrew Koenig in ctraps.pdf saying "In most languages, an array with n elements normally has subscripts ranging from 1 to n inclusive.")

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.


Expanding on the numeric string example cited above; numeric values and numeric strings may be used almost interchangeably in most cases.

<lang perl6>say 123 ~ 456; # join two integers together say "12" + "5.7"; # add two numeric strings together say .sqrt for <4 6 8>; # take the square root of several allomorphic numerics</lang>

123456
17.7
2
2.449489742783178
2.8284271247461903

You can run into problems though with certain constructs that are more strict about object typing.

A Bag is a "counting" construct. It takes a collection and counts how many of each object are within. Works great for strings.

<lang perl6>say my $bag = <a b a c a b d>.Bag; say $bag{'a'}; # a count? say $bag< a >; # another way</lang>

Bag(a(3) b(2) c d)
3
3

But numerics can present unobvious problems.

<lang perl6>say my $bag = (1, '1', '1', <1 1 1>).Bag; say $bag{ 1 }; # how many 1s? say $bag{'1'}; # wait, how many? say $bag< 1 >; # WAT dd $bag; # The different numeric types LOOK the same, but are different types behind the scenes</lang>

Bag(1 1(2) 1(3))
1
2
3
Bag $bag = (1=>1,"1"=>2,IntStr.new(1, "1")=>3).Bag

The different '1's are distinctive to the type system even if they visually look identical when printing them to the console. They all have a value of 1 but are respectively and Int, a String, and an IntStr allomorph. Many of the "collective" objects have this property (Bags, Sets, Maps, etc.) This behavior is correct but can be very jarring when you are used to being able to use numeric strings and numeric values nearly interchangeably.


Another 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>

Z80 Assembly

JP (HL)

This is a bit of misleading syntax. For every other instruction, parentheses indicate a dereference of a pointer. <lang Z80>LD HL,(&C000) ;load the word at address &C000 into HL LD A,(HL) ;treating the value in HL as a memory address, load the byte at that address into A. EX (SP),HL ;exchange HL with the top two bytes of the stack.

JP (HL) ;set the program counter equal to HL. Nothing is loaded from memory pointed to by HL.</lang>

Strangely enough, 8080 Assembly uses a more sensible PCHL (set Program Counter equal to HL) to describe this function. So this gotcha is actually exclusive to Z80.