
False Cognates and Syntax: The Importance of Syntax in Programming Languages - Swizec
http://www.txt.io/t-2eewd
======
mistercow
I disagree pretty strongly about the specifics of syntax being unimportant. If
I had no experience and were speculating based on common sense, I would say
"of course the details of the syntax don't matter". Hell, I might have even
said that a year ago.

Then I started using CoffeeScript. Now, don't get me wrong; CoffeeScript has
lots of little features that are very nice, like concise comprehensions and
bound functions, which are not mere details. But if you took away all of that
and left only the JavaScript with different syntax details, you would still
have something much more pleasant to read and write than JS. -> function
syntax, postfix conditionals, unless, on/off/yes/no, @, and "string
#{interpolation}" are all tiny "syntax details", and I simply cannot
overstress how much nicer they are than the JavaScript they trivially
transform into.

------
Natsu
For anyone who didn't know what if expressions look like in Erlang and had to
look it up, apparently they use a series of "guard sequences" and the first
one to be true determines the return value. If none of the sequences is true,
it creates an error. There's no "else" keyword, so you have to use true as a
substitute for it like this:

    
    
      is_greater_than(X, Y) ->
        if
            X>Y ->
                true;
            true -> % works as an 'else' branch
                false
        end
    

(Code from <http://www.erlang.org/doc/reference_manual/expressions.html>
section 7.7)

See also: [http://erlang.org/pipermail/erlang-
questions/2009-January/04...](http://erlang.org/pipermail/erlang-
questions/2009-January/040814.html)

~~~
babarock
You can do something similar in any language that support booleans as keys for
dictionaries/hashtables/maps/associative arrays/...

For instance in Lua:

    
    
        function abs(x)
            test = {
               [true]=-x,
               [x>0]=x,
               [x==0]=0}
            return test[true]
        end
    
        > print (abs(-3))
        3
    

To understand this code, think that `test[true]` is overwritten at
initialization time by the last predicate to evaluate to True.

It's not the best way to do this, but maybe it'will help understand the above
construct.

~~~
reycharles
There's a difference, though. This example evaluates every "branch".

~~~
reikonomusha
Make it even more ridiculous and wrap every branch in a lambda function, then
do

    
    
        test[true]()

~~~
mahcuz
That would only evaluate the original [true] key, though, right?

~~~
tgasson
No, `test[true]` will be reassigned to the given function each time the
condition evaluates to true. Calling `test[true]()` will execute that given
function.

------
adeelk
A programming language designer shouldn’t have to make accommodations for
users of whatever other languages are popular at the moment. If “return” is
the natural name for a keyword, then regardless of its semantics in other
languages, that is the best choice in the long term.

Technical note: even in languages where every expression has a value, it is
possible to have an `if` form that doesn’t require an `else` value: just
return nil. See, for example, Clojure: <http://clojure.org/special_forms#if>

~~~
joeyh
return was pretty clearly chosen in haskell to make its monadic do syntax look
even more like a regular procedural language. Names that better express what
return really does would be something like "pack" or "lift" or "pure".

A typical example of a confusing return in haskell is this. Note that this
always prints "hi".

    
    
      foo bar = do
        if bar > 5
           then plugh
           else do
             xyzzy
             return ()
        print "hi"
    

Here xyzzy and plugh return different types; since the if statement needs the
same type on both if branches, return has to be used to return a dummy value
of the same type as plugh (here assumed to be ()). But it only returns it to
the outside of the if; haskell's return does not influence control flow.

You do get used to this pretty quickly, but there's also a tendency to move
away from that style of haskell. I'd write the above more like this, using
guards rather than the if, and using void to force both plugh and xyzzy to
return the same type.

    
    
       foo bar = go >> print "hi"
         where
           go 
             | bar > 5 = void plugh
             | otherwise = void xyzzy

~~~
chimeracoder
I wouldn't say that that's that surprising, when you remember that 'do' in
Haskell is sort of analogous to defining and calling a function inline. If you
keep that in mind, 'return ()' makes perfect sense.

Return _does_ influence the control flow in the sense that it signals the end
of the monad - it's just that Haskell allows you to define and call a monad
('function') inline, which is not idiomatic in most procedural languages.

It's not too far removed from the following Python code:

> (lambda x: 5)(5)

Which, naturally, returns '5'. Lisp, of course, treats lambdas similarly;
however, writing a series of statements in Lisp (like progn) is not considered
idiomatic/'good' Lisp, whereas writing monads in Haskell is absolutely
necessary.

------
yummyfajitas
What I find to be the most annoying false cognate is attribute accesses which
are secretly method calls.

In [ x.y() for x in X], it's immediately obvious that x.y() is doing some
work. Not so obvious in [ x.y for x in X ].

This is a very practical concern - in Django code (both mine and other
people's), I see lots of unnecessary SQL queries all over the place because of
this.

Of course, Ruby, Scala, etc, are not immune to this criticism.

~~~
dkarl
The criticism does not apply to Scala because paren-less method calls are the
norm, and because () was never intended to distinguish between field access
and a method call. To assume x.y is a cheap operation in Scala would be to
apply an assumption from other languages that is not valid in Scala. (Using
parens for a zero-arg method in Scala communicates something else: by
convention, x.y() is used if y has side effects, and x.y is used otherwise. I
don't know how widely that convention is observed, though.)

Python is different because the style guide explicitly discourages [1]
computationally expensive accessor methods (as well as accessor methods with
side effects) specifically so that programmers can treat accessor methods the
same way they would treat a data field. Assuming that x.y is a field access in
Python is not supposed to lead to problems, and if it does, it's the fault of
the class implementer and not the user.

[1] [http://www.python.org/dev/peps/pep-0008/#designing-for-
inher...](http://www.python.org/dev/peps/pep-0008/#designing-for-inheritance)

------
reycharles
> In functional languages, however, where everything is an expression that has
> a value, the else block (or its equivalent) is mandatory. You simply cannot
> have an empty or missing else block. (Further, in languages with strict type
> systems, the else block has to have the same type as the if block,
> typically.)

The else clause is also mandatory in C, java, ... in if-expressions:

    
    
        int foo = <test> ? <then-clause> : <else-clause>;
    

They even have to have the same type!

------
saurik
AFAIK (as someone who studies programming languages, but not as a "real"
Haskell developer) the metaphor of "return" with respect to monads in Haskell
is that when you "bind" a function to a monad the result (the "return value"
as you might say in a many other languages) must itself be wrapped in the
monad (so returning really is putting something "into" the monad).

I personally do not feel like this is squinting and rotating your head 47
degrees: if you look at simple Haskell code examples the "return" function is
used in the same way as it would at the end of any normal C function. The
author of this article sees it that way, but that is just an opinion in not
backed up in the argument by the "false cognates" premise.

~~~
mokus
It's not hard to learn the difference, but the article's point in calling it a
"false cognate" is just to note that there _is_ an unexpected difference. I
don't believe the author's use of the phrase is correct, though. Cognates
don't have to mean the exact same thing in the different languages - they just
have to be derived from the same source. So a "false cognate" actually would
mean words that appear to have come from the same historical roots but don't
really. What makes "return" in Haskell a (true) cognate is something you
mentioned - its name was chosen intentionally as a _metaphor_ for return in
other languages, though it's not the same thing.

On the other hand, I think the article's point that it's tricky is valid. The
way it is usually used makes it appear that it is causing the procedure being
defined to exit. If a user thinks that's what it's actually doing (which they
tend to do when coming from other languages), they will try to write things
like "when (i == 0) (return x)", which does not mean what they think it means.

~~~
gliese1337
And here we have an example of a similar linguistic problem- polysemy (one
expression meaning multiple things) within a single language.

In philology / historical linguistics, "cognate" is a term relating strictly
to etymological origins (in which context English and German "gift" really are
true cognates). In typical middle/high-school foreign language classes,
though, "cognate" is usually used in the related-but-rather-different sense of
a word that ought to be easy to remember for the vocab test because it's sound
and meaning are both similar to a word in your own language, and a false
cognate is a word that looks or sounds familiar but means the wrong thing (in
which context English and German "gift" would be considered "false cognates"
for pedagogical purposes). Most true cognates in the technical sense would
never be presented as cognates to a beginning foreign language class.

~~~
aidenn0
I have heard the term "false friend" suggested as an alternative for "false
cognate" but I've never seen it used except when someone is trying to talk
about both.

------
Suncho
Hmm. Why is he calling them false cognates? False cognates don't share a
common origin whereas these words clearly do.

Anyway, for me, a good example of this phenomenon is when people come to C++
from a language like Java. In Java, you have to use "new" every time you want
to create an object, so these people tend to go around putting "new"
everywhere.

~~~
DeepDuh
I get your point, however Java came after C++, its usage of "new" is however
more consistant IMO. In that regard I don't see what either language designers
could have done. This post, if I understand it correctly, was a plea to
language designers.

~~~
Suncho
The usage of "new" in Java is consistent according to the rules of Java and
the usage of "new" in C++ is consistent according to the rules in C++. I don't
think you can say that one is more consistent than the other. To a C++
programmer, the usage of "new" in Java, on first blush, seems highly
inconsistent. Why use it for a String but not an int for example? The answer,
of course, is that Strings in Java are objects and ints aren't. In C++,
everything is an object.

"new" simply has a different meaning in Java even though the syntax is
similar. If I were designing Java, I'd leave out the "new" keyword altogether.
I see it as unnecessary to the semantics and confusing to C++ programmers, but
that's just me. =)

I agree that the post was a plea to language designers, but such a plea is
probably pretty useless and I didn't find it worth discussing. However, it can
be fun to relate to his frustrations by sharing a story from your own
experience.

Are you a Java developer? What's the most annoying behavior you see coming
from converted C++ developers?

~~~
DeepDuh
In C++ everything is an object? Since when? Or are you only calling the added
"++" components "C++" but not the included C syntax?

As for me, I don't call myself a [insert language]-developer. I use whatever
language suits best to design software. So far I have experience in many
common procedural, OO and markup languages (C,C++,Java,Obj C,CUDA
C,bash,python,VB,Matlab,Lotusscript,html+css) and I try to get experience in
functional languages as soon as I can get some time for that. What's
characteristic for me in Java is mainly its VM architecture. This makes it
useful when you need the flexibility of a 3rd generation language for easily
portable code (e.g. many business applications), however it has some
disadvantages that have hindered its success for consumer applications. The
main disadvantages IMO are the non-native feel of the GUI, maintenance and
compatibility problems of the Java VM and Oracle's business strategy as of
late. Java's syntax is just fine, I don't have any grudges about it. Knowing
Obj C well, I know what ugly syntax looks like, however I still like that
language as well (performance and frameworks are quite good, especially
compared to other mobile frameworks).

------
Dn_Ab
Mathematics also has many false cognates. I agree that they make the subject
harder than they should be for beginners by bringing in extra baggage. It
takes more effort to dampen an old association and compartmentalize a new one
than to form new ones.

A good example I can think of right now is Imaginary numbers and complex
numbers. The legendary Gauss said they would have been better named Lateral
Numbers.

But for programming languages avoiding false cognates is even harder as they
not just naming things, but also deciding structure and must draw from the
same pool of words as hundreds of other languages.

~~~
adeelk
Not sure I understand your point. Gauss is credited for the term “complex
number” as well.

------
dsego
A really good example the text doesn't mention is the awful C-like syntax in
Javascript. You have curly braces which denote block scope in C-like
languages, and yet in Javascript you have functional scope. Also, other
things, like the "new" keyword. The syntax hides the true nature of the
language.

------
raymondh
A false cognate in Python is super(). It does something _much_ different than
super in other languages -- calls to super may call some class that is _not_ a
parent of the class where the call originates.

------
spacemanaki
One problem is that there are only so many punctuation characters in ASCII,
and in addition to that, there are only so many English words that are general
enough to be used as keywords. Take Clojure as an example: assoc, do, and even
let are basically false cognates according to the OP, since they differ from
the same keywords in Common Lisp and Scheme, both a lot (assoc and do) and a
little (let). But if Rich Hickey had picked other words, just for the sake of
making it easier on beginners, I think the language would have suffered. This
is just another example of optimizing for the experience of beginners, rather
than for the experience of programmers who have taken the time to become
proficient with the language.

~~~
vorg
> One problem is that there are only so many punctuation characters in ASCII

You can use sequences of punctuation characters, e.g. <:= and =:> for
brackets, or =>> for an operator, or :*; for a separator. Or use punctuation
characters not in ASCII: there's hundreds of them in the symbols blocks of
Unicode.

~~~
spacemanaki
This is certainly true, but I don't think it offers much practically. I would
prefer to use a language that re-used or recycled keywords made up of letter
than use a soup of punctuation just for the sake of being different.

------
tinco
"Stop making things more difficult in the long term just because some whiners
are unwilling to learn the trivialities of a new syntax in the short."

The unwillingness of whiners is the number one reason for software to suck.
Why does a C11 compiler still compile Ansi C? It is utter crap, and whiners
are to blame.

edit: Conservativeness is the reason young developers don't learn C, why C#
beat Java and why everyone will have to learn a new language every few years
to keep on top of the curve.

If you see a flaw in a language you use, do you report it, contribute to a
thread, or do you just learn to live with it?

(sorry I forgot to mention I had added 2 paragraphs)

~~~
saurik
The problem with using this kind of rhetoric is that it is also "utter crap"
to require everyone to stop making progress on applications and high-level
systems because some "whiners" are "unwilling" to put up with a couple warts
in the languages they are using so that we can all instead go through tons of
working code to update it to some new syntax or language standard; one might
even, then, claim that it is this other set of whiners that "are to blame" for
why a lot of libraries in some languages (I'm looking at you, Python) have
never been able to get past puberty where they can start to crystalize into
high-level perfection, and are instead trapped in a perpetual quagmire of
"suck" while they scramble and fight to catch up to a shifting platform (and
often just get thrown out and rewritten over and over and over again by new
generations of whiners that insist that the old syntax is so unusable that
they refuse to touch it anymore and would rather just start fresh).

(edit: The author of the comment I responded to added another two paragraphs.)
C# beating Java is not something I think is as clear-cut as you seem to
believe (from my vantage, C# is used by Windows developers, and Java is used
by everyone else: the dividing line has little to do with syntax preferences
and much more to do with the quality of the IDEs and the integration of the
runtimes; before MS was legally required to stop distributing it, J++ was
gaining ground), but to the extent to which it is true you have to remember
that C# also is not making breaking language changes, and in fact didn't even
make drastic changes to Java (which one might argue is the legacy that C#
continued). In fact, C# was so compelling to a lot of us who adopted it early
because it had such amazing backwards-compatibility in the form of interop
with native C and C++ code with the ability to nearly natively interact with
our existing COM objects.

~~~
tinco
If I have a wart, I go to the GP to have it iced, perhaps it hurts for a
little while, but my skin is better off.

If a compiler removes a wart, why wouldn't you s/wart/scar/? Perhaps it's a
bit of a pain every compiler release, but in the end isn't your software a bit
more future-proof?

There is no need to stop making progress, this is why good software is open
source and on github.

New generations of whiners are better judges of what is and what is not
useable than old farts. The new generation has a glimpse of the future.

edit: I don't think native code interop is backwards compatibility, that would
imply C/C++ is somehow a thing of the past. My point was that as long as C/C++
are being adapted to the future, they will never be legacy and retain
relevance where their use is warranted. C# perhaps is not a winner in
adoption, but it is gaining ground on linux and osx. My observation that C#
beats Java is more in the syntax area.

~~~
saurik
This is a circular definition of "future-proof": if the compiler never
changed, then the software would have been future-proof the day it was
written, and the only reason it is not future-proof with the shifting compiler
is due to the threat that the compiler will eventually stop supporting the
feature. However, the assumption is generally and honestly should be (having
been proved in the field after decades of engineering) that there will be many
orders of magnitude more lines of code that exist outside of the compiler
(which is but one program often maintained by a single team of people) than
there ever will be in the compiler to maintain the old feature.

As for your comment about open-source... that doesn't help the problem of "all
the developers are spending their time updating and re-testing working code
rather than writing new code that relies on it", and specifically looking at
GitHub makes no sense. Finally, the "old farts" you are now denigrating have
more knowledge and experience of the kinds of failures you will run into, and
so far in my experience the people rebuilding systems end up learning, the
hard way, the same lessons that they could have just inherited (a rather
visceral lesson as I've watched my friend Yehuda work on Bundler over the
years, as he got to rediscover all of the things that APT solved over a decade
ago).

~~~
tinco
I don't think it is circular, if the compiler never changes, a new compiler
(for a new language) will come out. New generations might find the unchanged
compiler to be obsolete (as many think of C). Thus its future might be
mitigated. I don't believe introducing backwards incompatible changes is such
a big thing that developers have significantly less time to develop new
features. Look at Ruby 1.8->1.9 and Rails 2->3, yes it was a pain to upgrade,
for a little while. But the advantages are obvious, and it was not _that_ much
work. I was looking at github specifically because of their 'fork me'
attitude, which I admire. I don't advocate at all we should develop software
without looking at the past, if Yehuda didn't look at APT closely when he was
working on bundler, then that was a missed opportunity I agree. Do you think
Bundler should have somehow been an (compatible) improvement on APT instead of
a separate software? I myself have never used APT the way I use bundler, and I
have no idea if that would be possible.

------
danielroseman
I note that the writer has fallen into the trap of a false cognate - or, at
least, a near-homonym - by writing "demure" when he/she meant "demur".

------
ocharles
Any monad is an applicative functor, and any applicative functor provides
'pure', which does the same as return (lifting a value). The argument of the
name 'return' in a discussion on syntax doesn't quite seem appropriate to me -
it's an API/library problem, not one of syntax in the language. However, maybe
I'm in the camp who doesn't believe too strongly in the importance of syntax
though, as was mentioned in the opening paragraph...

------
mbq
Evaluate_some_stuff() in Prolog? Quaggy ground.

------
its_so_on
I had an idea for how to reduce errors when programming among many languages.
You choose a language to 'get in the zone with' (in your profile you have
already put all the languages you work with.) So, say you choose "C++". It
would flash some code and what it's supposed to do, and you put in whether
it's right (compiles and does what the comment says) or wrong (doesn't compile
or doesn't do what the comment says).

The key is that the examples it flashes exactly try to cover the code
mistakes/syntax errors (even things that don't compile!) that you would
normally create in the first few minutes or hours - before you're 'in the
zone' when coming from one of the other languages in the list. Things like
leaving off a semicolon when coming from Python to C++. You can be an
experienced C++ programmer, but after long hours of python, you need a period
of adjustment. Isn't this the most dangerous time to code?

So if you put that you want to get in the zone with C++, but in your profile
Python is listed, then some of the examples will be missing a semicolon while
being indented properly. If you put a language that uses eq instead of ==, .
instead of +, this is brought up a couple of times. All the things that
separate languages - so that in the first few error-prone hours of transition
you leave them out or forget them at times - are brought right to the front so
that you can produce much higher-quality code in your 'target language' after
'zoning up.' What do you guys think?

Pedagogically, it may be better not to flash incorrect code, but instead make
you write the code. But ask you to write code such that it explicitly tests
something that may trip you up coming from one of your other languages.

