Friday, June 26, 2015

On Uniformity of Syntax

One of the core philosophies behind Tuplex is to have a syntax that is as uniform and consistent as possible. This means that any semantic production should have the same syntax regardless of where it is placed. This has the benefits of:
  • Regular grammar - easier to learn, read, and write
  • Orthogonal semantics - helps keep semantic concepts independent and possible to recombine without "exceptions to the rule"
  • Making copy-paste easier - reduces need for editing copied/moved code and the associated risk of bugs

Consider the core language elements of function, type, and value expressions. If these are written the same way regardless of the form of statement they are part of, we've come a long way towards uniformity.

Functions, being one of the most complex constructs both syntactically and semantically, are a good illustration of how well a language achieves uniformity. Examine your favorite language and how many distinct function, method, lambda, and function pointer syntaxes it has...

The following Tuplex program shows how function type declaration, global and local functions, instance methods and lambdas, all have identical syntax, and also are fully interchangeable in assignment, function argument passing, and invocation.

My favorite aspect of this is how instance methods, free functions and lambdas all are handled equivalently and for example can be passed as argument to another function without that function caring about the difference. Closure capturing, when needed, is fully transparent.


type FuncType ( a : Int )->Int

square( a : Int )->Int { return a * a }

## these are equivalent:
aFunction : ( a : Int )->Int = square
bFunction : FuncType         = square
cFunction                   := square


type Multiplier {
    b : Int

    self( b : Int ) { self.b = b }

    mul( a : Int )->Int { return a * self.b }

    higher( f : FuncType )->Int {
        return f( self.b )
    }
}

main() Int {
    localFunc := ( a : Int )->Int { return a * 2 }

    value : ~Int = 2  ## ~Int means mutable 32-bit integer

    ## global function invocation:
    value = square( value ) ## 4

    ## local function assignment and invocation:
    tmpFn : ~FuncType = localFunc
    value = tmpFn( value )  ## 8

    ## global function assignment and invocation:
    tmpFn = aFunction
    value = tmpFn( value )  ## 64

    ## instance method assignment and invocation:
    mulObj := Multiplier(2)
    tmpFn = mulObj.mul
    value = tmpFn( value )  ## 128

    ## higher order function and inline lambda:
    value = value + mulObj.higher( ( a : Int )->Int { return a+1 } )

    return value  ## process returns 131
}

Edit: Updated example to reflect the new '->' syntax.