Lunar Programming Language

by David A. Moon
January 2017 - January 2018



Destructuring

The name in a constant definition, the iteration variable in an emitter in a for statement, or the name in a formal parameter declaration can be generalized to a destructuring rather than a simple name. It executes a constructor-like function call "in reverse" to bind names to parts of an object that would have been constructed by that function call.

The function call must be written in function(parameters) syntax. Due to syntatic ambiguity, it cannot be written in operator syntax. As a special exception, a list display [members] can be used as an abbreviation for list(members).

For example,

def [x, y, z] = f(a, b, c)
expects f(a, b, c) to return a list of three members and binds x, y, and z to the members of the list.

The function invoked on the left-hand side, in this case list, must have a destructuring method defined, e.g. by defdestructure.

Nested Destructuring

An actual parameter in a destructuring can itself be a function call rather than simply a name. In this case, the corresponding piece of the right-hand side will itself be destructured.

Named parameters can be used in a destructuring that calls a function (not list) that takes named parameters.

A rest parameter (trailing ...) can be used when destructuring certain functions (including list).

Destructuring Constant Definition

def function_call = value computes value and then defines constants whose names are the actual parameters of function_call and whose values are pieces of the result.

Variable definitions and assignment statements do not support destructuring.

Destructuring Formal Parameters

The name of a formal parameter in a method or class definition can be a function call rather than simply a name. In this case, the corresponding actual parameter or default will be destructured when the method or constructor is called.

Generic formal parameters do not support destructuring.

Destructuring Syntax and its Parsing

The syntax of destructuring could have been defined by:

;; The result of parsing a destructuring
constant:
defclass destructuring(named: name       name,                          ; the function
                              positional list[name|destructuring],      ; positional parameters
                              named      map[name, name|destructuring], ; named parameters
                              rest       name|destructuring|false)      ; rest parameter or false

;; Definition of destructuring syntax
defsyntax destructuring
        name [ "(" [ { [ keyword ] destructuring & "," }+ [ "..." ellipsis_flag ] ] ")" ] |
        "[" [ { destructuring & "," }+ [ "..." ellipsis_flag ] ] "]"
  => if not destructuring
       ;; Just a name, no parentheses after it
       name
     else
       def positional = stack([ d for d in destructuring, k in keyword if not k ]...)
       def named      = { k.name => d for d in destructuring, k in keyword if k }
       def rest       = ellipsis_flag and pop!(positional)
       destructuring(name:       name or name(#list, modules.lunar),
                     positional: list(positional...),
                     named:      named,
                     rest:       rest)

Defining Destructuring Behavior

Destructuring behavior is defined using defdestructure which attaches to a function a destructuring method that accepts three parameters and returns a sequence of tokens or expressions, the same as a macro returns, which will be wrapped in a block. The destructuring method is called at compile time. A function's destructuring method is entirely separate from its regular methods.

The parameters are:

  1. A sequence of the actual parameter expressions in a call to the function.
  2. The value to be destructured, always an expression with no side-effects. This is usually a name.
  3. A function that accepts two parameters: left-hand-side and right-hand-side. If the left-hand-side is not a name, the function will perform nested destructuring. This function returns a sequence of tokens or expressions to bind the left-hand side to the right-hand side.

The result sequence is mainly composed of code returned by the function parameter. Other code, such as definitions of temporary variables, can be added.

The defdestructure statement takes two formal parameter lists. The first one is applicable to the actual parameter expressions of the function call on the left-hand side. The second one accepts the value and function parameters. If the name of the type of acceptable values is not the same as the name of the function, it can be specified using a type-suffix on the first parameter in the second formal parameter list; this does not specify the type of the formal parameter's value, which is always expression.

In the body of the defdestructure statement, the name context is in scope, similar to the body of defmacro. Its value is a context for creating temporary names that will be hygienically isolated from other code.

Destructuring is pre-defined for lists. The right-hand side can actually be any sequence, such as a list! or a stack. The left-hand side can be an invocation of the list function or a [...] list display which parses as an invocation of the list function.

For example, list destructuring could have been defined by

defdestructure list(members...) (rhs sequence, setter)
  def position = name("position", context)
  `assert $rhs.length = $(members.length)
   def $position := iterate($rhs)
  ` +
  for member in members, first? = true then false using append
    if not first?
      append `$position := iterate($rhs, $position)
             `
    append setter(member, `next($rhs, $position)`)
Thus
def [a, b] = f(x)
would expand into something like
def temp = f(x)
assert temp.length = 2
def position := iterate(temp)
def a = next(temp, position)
position := iterate(temp, position)
def b = next(temp, position)
where temp and position are hygienically invisible and assert, ., :=, iterate, and next are hygienically bound to their usual values regardless of any local bindings in scope.

A constructor expression for any user-defined type can be the left-hand side in destructuring if defdestructure has been done to attach a destructuring method to the constructor function or the type was defined by the simple form of defclass, which automatically defines destructuring of a constructor call to extract the slot values from an instance of the class.

String Destructuring

Possible future feature: destructuring a string interpolation.


Previous page   Table of Contents   Next page



Creative Commons License
Lunar by David A. Moon is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.
Please inform me if you find this useful, or use any of the ideas embedded in it.
Comments and criticisms to dave underscore moon atsign alum dot mit dot edu.