Language Design: Unified Condition Syntax

Published on 2018-01-21.

Idea

Replace the different syntactic forms of

  • if expressions,
  • pattern matching and pattern guards,
  • if-let constructs

with a single, unified condition syntax that scales from simple one-liners to complex pattern matches.

Motivation

The intention is to cut the different syntax options down to a single one that is still easily recognizable by users, not to minimize keywords (i. e. a == b ? c : d) or turn conditions into methods (like Smalltalk).

Principles

  • The condition can be split between a common discriminator and individual cases.
    • This requires doing away with mandatory parentheses around the conditions.
    • This strongly suggests using a keyword (then) to introduce branches, instead of using curly braces, based on readability considerations.
  • The keyword if is chosen over other options like match, when, switch or case because it is keyword the largest number of developers are familiar with.

Examples

The following examples assume that the language has indentation-sensitive syntax to ensure unambiguous parsing.

Languages without indentation-sensitve syntax require either mandatory braces around the bodies of then branches, or ending then branches explicitly, for instance with end or a ,.

simple if expression
if x == 1.0                         /* same as */
then "a"                            if x == 1.0 then "a" else "z"
else "z"
one comparison operator on multiple targets
if x ==                 if x                    /* same as */
  1.0 then "a"            == 1.0 then "a"       if x == 1.0      then "a"
  2.0 then "b"            == 2.0 then "b"       else if x == 2.0 then "b"
      else "z"                   else "z"       else                  "z"
different comparison operators, equality and identity
if x                                /* same as */
  == 1.0 then "a"                   if x == 1.0      then "a"
  eq NaN then "n"                   else if x eq NaN then "b"
         else "z"                   else                  "z"
method calls
if xs                               /* same as */
  .isEmpty       then "e"           if xs.isEmpty            then "e"
  .contains(0.0) then "n"           else if xs.contains(0.0) then "n"      
                 else "z"           else                          "z"
pattern matching (is), introducing bindings ($)
if alice
  .age < 18                         then "18"
  is Person("Alice", $age)          then "$age"
  is Person("Bob", _)$person        then "{$person.age}"
                                    else "0"
pattern matching using “if-let”12
if person is Person("Alice", $age)
then "$age"
else "o"
wildcards (_) and pattern guards
if person
  is Person("Alice", _)             then "alice"
  is Person(_, $age) && age >= 18   then "adult"
                                    else "minor"

Further Considerations

A reasonable question that might be asked is whether this design can be extended to also handle thrown exceptions, and whether such an extension could completely replace the try-catch-finally idiom.

One language that has done something similar is Ocaml, which has extended its pattern matching syntax/semantics.

One option might be something along the lines of

if readPersonFromFile(file)
  throws[IOException]($ex)        then "unknown, due to $ex"
  is Person("Alice", _)           then "alice"
  is Person(_, $age) && age >= 18 then "adult"
                                  else "minor"

This might require adding some amount of language magic to deal with the throws construct though, depending on the expressiveness of the core language.

  1. Rust – https://doc.rust-lang.org/book/second-edition/ch06-03-if-let.html 

  2. Swift – https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/OptionalChaining.html