What kind of syntax looks cool but is difficult for people to understand?

If you’ve been doing code for a while, you may have heard of code golf which is the practice of trying to write algorithms in as little as characters possible. Here, at Exercism, we generally discourage it because in general it goes against adopting fluency and all (defacto) standards.

It’s (sometimes) fun though.

However, that made me think about a lot of things a lot of us may or may not do in the languages we feel confident or competent in, that are actually hard for beginners (of that language) to understand.

Benign operators

An example would be:

!!value
# converts to a boolean

This seems benign, but if you come from a language that only has true and false and not thruthy and falsy, or a language that uses ! not as not but as assert, you may not understand that this is the equivalent of the pseudo code value to boolean.

Another example may be the JavaScript:

const date = new Date()
+date

…which will give you the unix timestamp of that date. The clearer way to write this is date.getTime(), or if you must, Number(date) because its intend is clearer for more people, and .getTime() is easier to find in documentation than plus operator on a Date.

Complex non-operators

Some languages have cool complex operators, such as the spaceship operator in Ruby:

a <=> b

What this does is similar to a.compareTo(b) in other languages. It effectively tells you if:

if a < b then return -1
if a = b then return  0
if a > b then return  1
if a and b are not comparable then return nil

This is an actual operator in Ruby, but --> looks like an operator in C++ and is not:

    int x = 10;
    while (x --> 0) // x goes to 0
    {
        printf("%d ", x);
    }

Whilst this looks like the downto operator, there is no operator --> in C++. It’s a different way of writing x-- > 0 which is subtract one from x and greater than combined.

Your examples?

What I am looking for are seemingly benign examples that have a much clearer alternative in your language, and complex combinations that are confusing but fun. With one exception: RegExp is generally considered information dense and thus isn’t something you need to list as it’s difficult for people to understand by its very nature.

2 Likes

First thing that comes to mind in Ruby…

1..9

# vs

1...9

I’ve been writing them for ~15yrs and still have no idea which is why.

I’m not sure if this counts as an example as I don’t know what the easier syntax would be, but probably two sensibly named methods.

Hah. Ranges! I think that is a very good example because this is foreign to people.

(from..to)
# => Range.new(from, to)
# includes to

(from...to)
# => Range.new(from, to, true)
# excludes to

I usually remember by remembering that the third dot “eats” the last value.

I think this wouldn’t fall under either because it is an “operator” and it’s not “seemingly benign”. I guess we need a third category just weird.

1 Like

FORTH, in general, is difficult for people to understand because very few people can naturally think in Reverse Polish Notation. I think the 8th track documentation needs to more firmly encourage programmers to use SEDs (Stack Effect Diagrams). I’ve played the student on the 8th track. One of the other mentors (@Caraciola) has been pointing out the lack in my iterations.

LATER

I’ve just yanked this from comp.lang.forth. Even though I now have some experience reading FORTH (albeit the 8th dialect), this makes very little sense to me.

: (int-;]) ( some-sys lastxt -- ) >r hm, wrap! r> ;
: (;]) ( some-sys lastxt -- )
>r
] postpone ENDSCOPE third locals-list ! postpone ENDSCOPE
finish-code hm, previous-section wrap! dead-code off
r> postpone Literal ;

: int-[: ( -- flag colon-sys )
wrap@ ['] (int-;]) :noname ;
: comp-[: ( -- quotation-sys flag colon-sys )
wrap@ next-section finish-code|
postpone SCOPE locals-list off postpone SCOPE
['] (;]) :noname ;
' int-[: ' comp-[: interpret/compile: [: ( compile-time: -- quotation-sys flag colon-sys ) \ gforth bracket-colon
\G Starts a quotation

: ;] ( compile-time: quotation-sys -- ; run-time: -- xt ) \ gforth semi-bracket
\g ends a quotation
POSTPONE ; swap execute ( xt ) ; immediate 
2 Likes

Can you show us some examples that match the topic?

There are many languages with difficult syntax, but that’s neither benign nor “complex but not an operator”. However as FORTH is difficult by itself I’d assume there to be quite a few pitfalls which may be interesting to see for others.

bash.

Pretty much most of it.

A couple of examples

  • Escaping slashes in a string (for example, to safely use a shell variable in a s/// command in sed
    s="Hello/World"
    echo "${s//\//\\/}"      # => Hello\/World
    
  • look up a value in an array, and use a default value if the index is not there
    echo "${arr[$idx]:-$default}"
    

But, shockingly, bash can be (dare I say) beautiful. Using functions and “nameref” arguments, the heart of my two-bucket solution looks like

        if      is empty first
        then    fill first

        elif    is full second
        then    empty second

        else    pour from first to second
        fi
2 Likes

One of my pet peeves: languages that use 0/1 for boolean values, and code that abuses it:

sum += i % 2 ? double(digit) : digit
1 Like

Clojure’s threading macros. It solves the problem of having many nested expressions in Lisp, but if you don’t know what it’s doing it would make no sense.

Say you want the sum of the first 10 even squares. You could do it like this:

(reduce +
   (take 10
    (filter even?
      (map #(* % %)
        (range)))))
1140

range is an infinite sequence of integers, we map * on itself, filter the evens, take the first 10, and sum them with reduce. But it’s awkward to read because you have to trace the execution backwards (from the inside out). Using the ->> (thread-last) macro we can write it like this:

(->> (range)
     (map #(* % %))
     (filter even?)
     (take 10)
     (reduce +))
1140

It expands to the same code, by taking each list after (range) and inserting it as the last item of the following list.

1 Like

I think any Lisp with macros would be perfect for this topic, because you can change almost everything in the language itself.

Depending upon the Lisp a good chunk of the “language” you see is already macros.

2 Likes

When I was going through the common-lisp track, loop baffled me at first for feeling so different. I did come to appreciate its power, but it sure does stick out as a separate beast.

Speaking of separate beasts, expr in Tcl – you need to call a command that implements arithmetic as a DSL

@glennj can you post an example of expr ?

I have another one that seems benign but really isn’t.

Nested ternaries

The ternary operator in PHP, JavaScript, etc. takes three inputs. The left side is the expression to evaluate, the operator is the ? (question mark), the right

// php
$number = 10;
echo $number > 0 ? 'Positive' : $number < 0 ? 'Negative' : 'Zero';
// javascript
const value = 10;
console.log(value > 0 ? 'Positive'  : value < 0 ? 'Negative' : 'Zero' )

These do not give the same answer. This is also why since PHP 7.x it’s been deprecated.

1 Like

Can you show how they get parsed?

In a language like C

x = 4 + 3 + 2;

But with Tcl, the arithmetic expression is an argument to the expr command

set x [expr {4 + 3 + 2}]

Tcl syntax is like shell or lisp in the sense that the “command” must come first followed by arguments. Variable assignments use the set command. Infix operations are not supported by the language because 4 is not a command. Tcl does implement math operators as commands in the ::tcl::mathop namespace, so we can do this

set x [::tcl::mathop::+ 4 3 2]
# or
namespace import ::tcl::mathop::*
set x [+ 4 3 2]

Lisp-y enough?

There’s also a ::tcl::mathfunc namespace

set pi [expr {atan(1) * 4}]
# or
namespace import ::tcl::mathop::*
namespace import ::tcl::mathfunc::*
set pi [* [atan 1] 4]

What kind of syntax looks cool but is difficult for people to understand?

Well, it’s not cool but at least it’s difficult to understand!

$a = 2;
echo ($a == 1 ? 'one' :
     ($a == 2 ? 'two' :
     ($a == 3 ? 'three' :
     ($a == 4 ? 'four' : 'other'))));    # prints 'two'

This is what we would expect right?
Why do we expect this? Well… because in most languages x ? y : z is right associative. In PHP, however, the above would NOT print two, but rather, as it’s left associative, as:

<?php
$a = 2;
echo (((($a == 1  ? 'one' :
         $a == 2) ? 'two' :
         $a == 3) ? 'three' :
         $a == 4) ? 'four' : 'other');   # prints 'four'

(Note the extra brackets).

So if we evaluate this per step, you’d get:

$a = 2;
echo (((($a == 1  ? 'one' :
         $a == 2) ? 'two' :
         $a == 3) ? 'three' :
         $a == 4) ? 'four' : 'other');  

// Evaluate $a == 1,  therefore $a==2
echo ((((FALSE    ? 'one' :
         TRUE)    ? 'two' :
         $a == 3) ? 'three' :
         $a == 4) ? 'four' : 'other');

// First ternary collapses to `TRUE`
echo ((( TRUE     ? 'two' :
         $a == 3) ? 'three' :
         $a == 4) ? 'four' : 'other');

// ...therefore it uses "two"
echo ((  'two'    ? 'three' :
         $a == 4) ? 'four' : 'other');

// "Two" is "thruthy", so it becomes "three"
echo (    'three' ? 'four' : 'other');

// ...which is "thruthy".
echo 'four';