The syntax thread!

:smiling_imp:

What is your preferred syntax style and why?

Perhaps we can add examples and use the code below as a simple reference point (how would you write it in your chosen language?) but feel free to add additional examples if you feel they show off your preferred syntax well too.

Ruby

class Ball
  def initialize(colour)
    @colour = colour
  end

  def red?
    @colour == "red"
  end
end

my_ball = Ball.new("green")
my_ball.red?
# false
1 Like

One thing I like about the syntax of typical OO languages, and ā€œpipelinedā€ languages like Elixir, is the concept of ā€œtake this, do this with it, then do that with it, then do the other thing with itā€. That concept really helped me pick up Elixir pretty quickly after realizing that |> was very much like . just without the implicit matching on whatever kind of thing (Ruby object, Elixir data) you had at that time.

5 Likes

Whatever people say I prefer languages with block delimiters instead of off-side rule. Off-side rule is okayish in configuration languages and other languages that describe trees (like HAML or Slime), but IMHO do not works well in programming languages (sorry Python, I never really liked you anyway).

4 Likes

Oh there are so many languages that I like so much syntax of, but I will start with FORTH, it has the ā€˜purestā€™ and most simple syntax, itā€™s just a stream of WORDS. Anything can be a word except whitespaces, whitespace delineates WORDS, and a WORD is any sequence of characters excluding whitespace. ^.^

A WORD you can think of as a function in FORTH, although whether thatā€™s a built-in or so can be debatable, and you can rebind WORDā€™s, and WORDā€™s are only run one at a time, so a single WORD will be run to completion before it even looks at the next. You can write a fully conformant FORTH interpreter in just a few hundred lines of code in a verbose language (like a dozen or so in Python). FORTH runs on satelliteā€™s even to this day to more things than people would expect, it is fast and short and easy to update.

The basic concept of FORTH is there is a STACK, which you can push to and pop from, though there are WORDS that allow you do to things like assign variables and such as well in most implementations.

For example DUP will duplicate what is on top of the stack, so if the number 1 was on it, now there will be two 1's. To put a 1 on the stack you can just use the WORD 1, which defaults to a built-in function that just parses that integer and puts it onto the stack, all such integers are ā€˜defaultedā€™ in such a way, though in some implementations you can override that if you so wish. + is a built-in function (again, can be overridden by the user, but ew in this case?), it just pops the top two items off the stack and puts on to the stack the added value of the two things it popped off.

So if you run:

1 2 +

Then you will end up with 3 on the stack as it first pushes a 1 when it runs 1, then pushes a 2 when it runs 2, then it pops the 2 and then the 1 and pushes a 3 when it runs the + WORD.

To define a function you use the : WORD, which pops WORDā€™s from the input stream until it hits a ; WORD and stores them into the named WORD. So a function like : DOUBLE 2 * ; would define a DOUBLE word that pushes a 2 then runs *, which will pop the top two stack values, multiply them together, and push on the result, so doing 3 DOUBLE would then have 6 on the stack. You can optionally have a comment after the name in the function definition inside of (...) so by convention the stack input along with -- then the stack output is generally put there, so for DOUBLE WORD youā€™d document it like : DOUBLE (a -- b) 2 * ; to state it pops one input and pushes one output, and of course you can write text to describe documentation as well. For note, even things like ; are just functions too, though for ; itā€™s : ; POSTPONE EXIT REVEAL POSTPONE [ ; IMMEDIATE, where the WORD POSTPONE means to take a WORD that you would normally call immediately and instead postpone it, like take the pointer to a function in most languages. EXIT means to exit compilation mode, REVEAL will reveal the new WORD so it can be used, etcā€¦ FORTH itself needs very very few built-ins defined (though there are usually a lot more for speed reasons), and even the act of defining a function is actually taking the function pointers of WORDS and inlining them, usually directly to machine code (often with some simple optimizations to inline things), hence why FORTH is usually quite FAST. ^.^

For note, FORTH is usually case insensitive, but full-caps is most often used by convention except in strings (" WORD).

Letā€™s see a simple popular tutorial set of code:

: STAR	           [CHAR] * EMIT ;
: STARS	           0 DO STAR LOOP CR ;
: SQUARE	       DUP 0 DO DUP STARS LOOP DROP ;
: TRIANGLE	       1 DO I STARS LOOP ;
: TOWER ( n -- )   DUP TRIANGLE SQUARE ;

This allows you to call, say, 4 TOWER and it will print out:

*
**
***
****
****
****
****

For each function:

  • : STAR [CHAR] * EMIT ; This defines a WORD named STAR, the [CHAR] word takes the next WORD and treats it as a character array (basically a string), so * in this case, and then EMIT's it to the screen. So calling STAR will just print a *. For note, [CHAR] is ansi forth setups rather than the FORTH standard, to be standard compliant just change [CHAR] * to 42. ^.^
  • : STARS 0 DO STAR LOOP CR ; This defines a WORD named STARS, then pushes a 0 onto the stack, then calls the WORD DO, which will pop the top two numbers off the stack and consume WORDā€™s up until LOOP is encountered, then run them repeatedly the number of times as the number popped off stack (0 to N, where N is what was popped), then lastly it prints a carriage return (\n in other words). So calling 3 STARS will print ***\n.
  • : SQUARE DUP 0 DO DUP STARS LOOP DROP ; This defines a WORD named SQUARE, which first DUPlicates whatā€™s on top of the stack, then pushes a 0, then does a loop of calling STARS that number of times after duplicating the input again, then drops the number thatā€™s on the stack, so calling 2 SQUARE would print **\n**\n.
  • : TRIANGLE 1 DO I STARS LOOP ; This loops from 1 to the passed in number and calls STARS with each iteration of I (the internal variable set by DO for its loop by default), so calling 4 TRIANGLE would print *\n**\n***\n, so a triangle of one size less than the number pass in
  • : TOWER ( n -- ) DUP TRIANGLE SQUARE ; This just duplicates the input, then calls TRIANGLE (which pops one of those off) then calls SQUARE (popping the original off), and thatā€™s it.

But FORTH is pure function, most programs in FORTH will read a lot like english sentences, you can define any kind of DSEL as you want, etcā€¦ FORTH and LISP have very similar ideas, both can create DSELā€™s with impunity, but where Iā€™d say FORTH is a purity of function, LISP is a purity of form, they do things in very different ways.

FORTH being so short and easy to implement even in raw machine code makes it really common to bootstrap micro-projects that need to be reprogrammable, plus itā€™s very fun to program in as it is so different than essentially any other language. ^.^

3 Likes

Ha, yeah, writing a (subset) FORTH interpreter is one of the exercises on some of the language tracks on Exercism.io. Lots of fun.

3 Likes
  • I donā€™t like indentation based syntax, even if I like Elm.
  • I wish Ruby, Crystal and Elixir had { } instead of do-end, except the anonymous functions in Elixir.
  • I wish parenthesis were not optional in Ruby, Crystal and Elixir, even if I leave it often now that they are optional.
  • In my opinion Goā€™s syntax is the most clear (by clear I donā€™t mean elegant). I am also OK with Goā€™s if err != nil { ... } but I wish either Go had a ternary operator or gofmt allowed the error check on one line, like above.
3 Likes

Have you looked at rust? Not indentation based, uses {/}, parenthesis are not optional, etcā€¦ ^.^

3 Likes

Yes, Iā€™m currently learning Rust, and so far Iā€™m liking it. I donā€™t give it much time though. I started with Manning - Rust in Motion. The learning curve is of course steep, but the language is powerful.

3 Likes

Hands down my favorite syntax is ML family of languages ocaml/f#/haskell/Elm

let registerNewUser dbClient =
    request ( fun inputGraph ->
      Newtonsoft.Json.JsonConvert.DeserializeObject<UserRequest>(inputGraph.rawForm |> System.Text.ASCIIEncoding.UTF8.GetString)
      |> hashPassword
      |> registerWithBson dbClient
      |> RealWorld.Convert.userRequestToUser
      |> jsonToString
      |> Successful.OK
    )

Why? Whitespace is a natural idiom in written languages. We use indentation in table of contents, spaces between words, paragraphs, etc. So adding glyphs like brackets or beginā€¦end in a language is completely redundant. In ML family languages all symbols have contextual meaning. Parenthesis are used to group ā€œstuffā€ together. Brackets are added when the need to indicate a ā€œblockā€ only in certain contexts.

Simply put, if whitespace significance didnā€™t matter in programming languages, then remove all of the spaces in your C family languages that the compiler is happy to build and see if you like it LOL

That being said, I donā€™t dislike C-family languages (I program in swift and kotlin on my job) but ML syntax is definately the most pleasant for me to read due to the low visual ā€œnoiseā€.

4 Likes

I definitely agree with you ā€“ I found OCaml very pleasant. But still, only using whitespace to mentally parse code is much harder for me compared to the block-y and glyph-y delimiters in other languages.

Maybe itā€™s about a habit though.

2 Likes

I started learning Rust with the same resource. So far itā€™s doing a good job at explaining Rust to me and I am loving the syntax. Iā€™ll start reading Rust in Action once I am done.

3 Likes

ML languages definitely have the best syntax as far as Iā€™m concerned, however Iā€™ve also grown to enjoy Erlangā€™s syntax.

3 Likes

OCaml doesnā€™t use any whitespace for anything other than to separate tokens. There is no significant whitespace (excepting one odd annoying bit about top level letā€™s where inā€™s are optionalā€¦).


Erlangā€™s Prologā€™ish style syntax is nice though yeah.

1 Like

I really like Elixir pipe operator |>.

I know one can easily abuse of it and that we should not pipe to pipeā€¦ but sometimes I canā€™t help. :sweat_smile:
Here is an example that I use in my Ecto Contexts to generate some a rank that will be used to sort a collection like for example ā€œcategories listā€. By default when adding a new item to the collection, the function will check the higher rank in a given DB Table, then add +1. The form will be populate with this rank but the user can change it then or later. But too much talk, here is the function. :sweat_smile:

  def get_auto_rank(schema) do
    from(s in schema, select: max(s.rank))
    |> Repo.one()
    |> Kernel.||(0)
    |> Kernel.+(1)
  end
2 Likes

I completely sympathize, I abused it for a while before I stopped. :smiley:

That being said, IMO your code will be more readable and self-explanatory if written like so:

def get_rank(schema) do
  from(s in schema, select: max(s.rank))
  |> Repo.one()
end

def get_rank_or_default(schema) do
  (get_rank(schema) || 0)
  |> Kernel.+(1)
end

At least thatā€™s what I would say if I was code-reviewing you. :slight_smile:

3 Likes

Not:

def get_rank_or_default(schema) do
  (get_rank(schema) || 0) + 1
end

? Am I missing something that would make that not work?

2 Likes

It will work, of course. What I offered is a compromise between brevity and immediate readability ā€“ quite the subjective one too. Yours can also be treated as very readable!

2 Likes