Metaprogramming

Blocks

Aya provides a basic data structure for representing code called a block. A block is a list of instructions. Internally, every Aya program is a block.

aya> {1 1 +}
{1 1 +}

Evaluate it with the ~ operator

aya> {1 1 +} ~
2

By default, blocks assigned to variables are automatically evaluated when de-referenced. Use .` to get the block without evaluating it.

aya> {1 1 +} :a
{1 1 +}
aya> a
2
aya> a.`
{1 1 +}

Split a block into parts using the .* operator.

aya> {3 4 *} .*
[ {3} {4} {*} ]

The same operator is used to join a list into a block:

aya> [ {3} {4} {*} ] .*
{3 4 *}

.* automatically converts data into instructions

aya> [ 3 4 {*} ] .*
{3 4 *}

For example, make_adder is a function that takes a number N and creates a block of code that adds N to its input

aya> { {+} J .* }:make_adder
{{+} J .*}
aya> 5 make_adder :add_five
{5 +}
aya> 4 add_five
9

Macros

In Aya, programs are evaluated from left to right

aya> 1 2 +
3
aya> 1 2 + 4 *
12

Above, the + and * operators read data from their left. When evaluating +, everything to the left is considered data and everything to the right is considered instructions.

     1 2 + 4 *
<-- data | instructions -->

All standard operators and functions operate only on data; that is, things to their left.

A macro is a function that operates on instructions; or things to its right. Macros may also operate on data and instructions.

For example, struct is a macro that reads two instructions: the type name and the list of member variables.

aya> struct point {x y}
<type 'point'>

if is a macro that reads 3 instructions to achieve behavior similar to if keywords from imperitive languages

aya> if (1) {"true!"} {"false!"}
"true!"

The :` operator is used to create macros. It takes 2 data arguments. A block B and an integer N. When evaluated, it will wrap each of the next N instructions in a block (converting them to data) then wrap the whole thing in a list. Then it will run B after the newly created block.

aya> { "data block" } 1 :` instruction
[ {instruction} ] "data block"
aya> {1} 2 :` 3 +
[ {3} {+} ] 1

Macro Example

Lets define a macro apply that applies the instruction after it to each element of a list.

aya> ["three" "two" "one"] apply .upper
[ "THREE" "TWO" "ONE" ]

First we use :` to capture the instruction we want to apply then use the ~ operator to unwrap the instruction list

aya> ["three" "two" "one"] { } 1 :` .upper
[ "three" "two" "one" ] [ {.upper} ]
aya> ["three" "two" "one"] { ~ } 1 :` .upper
[ "three" "two" "one" ] {.upper}

We use the map operator O to apply the block to each element of the list

aya> ["three" "two" "one"] { ~ O } 1 :` .upper
[ "THREE" "TWO" "ONE" ]

Now we can replace .upper with the reverse operator U to reverse the strings in the list instead

aya> ["three" "two" "one"] { ~ O } 1 :` U
[ "eerht" "owt" "eno" ]

Finally, we can remove our example data and define our macro.

aya> { { ~ O } 1 :` } :apply
{{~ O} 1 :`}

Usage:

aya> ["three" "two" "one"] apply .upper
[ "THREE" "TWO" "ONE" ]
aya> ["three" "two" "one"] apply U
[ "eerht" "owt" "eno" ]
aya> ["three" "two" "one"] apply .[0]
"tto"

Apply multiple instructions by wrapping them in ()

aya> ["three" "two" "one"] apply ("!" +)
[ "three!" "two!" "one!" ]