aya-lang / aya

Pocket sized programs
MIT License
54 stars 3 forks source link

Argument unpacking for lists #88

Closed nick-paul closed 11 months ago

nick-paul commented 1 year ago

This PR adds a new syntax for block arguments which allows unpacking a single list argument into multiple variables. It is inspired by the splat/slurp syntax in other languages such as Julia or Python.

Basic Examples

To unpack a single list argument into separate variables, wrap multiple variable names in square brackets in a block header.

.# Basic example
aya> [1 2] {[a b], "a is $a, b is $b"}~
"a is 1, b is 2" 

aya> .# Can be used for any block including map operations, etc..
aya> [[1 2][3 4]] :# {[a b], "a is $a, b is $b" :P };
a is 1, b is 2
a is 3, b is 4

aya> .# Nesting works too!
aya> [1 [2 3]] {[a [b c]], "a is $a, b is $b, c is $c"}~
"a is 1, b is 2, c is 3" 

aya> .# Like any other block argument, typing is supported
aya> ["A" "B"] {[a::num b::str], }~
        Expected:::num
        Received:"A"
["A" "B"] {[a::num b::str], }~
~~~~~~~~~~~~^

Slurp

Use ~ anywhere in the argument list to "slurp" up the remaining list items not captured by the explicit arguments

aya> .# Assign the first element to a, the rest of the list to b
aya> [1 2 3 4 5] {[a b~], "a is $a, b is $b"}~
"a is 1, b is [ 2 3 4 5 ]" 

aya> .# The slurp can appear anywhere in the argument list (only once)
aya> [1 2 3 4 5] {[a b~ c], "a is $a, b is $b, c is $c"}~
"a is 1, b is [ 2 3 4 ], c is 5" 

aya> .# Slurp can be empty
aya> [1 2] {[a b~ c], "a is $a, b is $b, c is $c"}~
"a is 1, b is [ ], c is 2"

Errors & Catchall

One variable operator may be included as the last item in the argument list. If provided, the entire list is stored as that variable in addition to the normal unpacking.

aya> [1 2] {[a b :l], "a is $a, b is $b, l is $l"}~
"a is 1, b is 2, l is [ 1 2 ]" 

If the input list size if different than the number of items in the list, throw an error

aya> [1 2] {[a b c], } ~
Cannot unpack [ 1 2 ]. List length does not match number of args

[1 2] {[a b c], } ~
~~~~~~~^

If the input size if different but a catchall is provided, suppress the error but do not define the slurp variables. Only the catchall is defined

aya> [1] {[a b~ :l], l [] = {"l is empty"} {"a is $a, b is $b"} .?}~
"a is 1, b is [ ]" 
aya> [] {[a b~ :l], l [] = {"l is empty"} {"a is $a, b is $b"} .?}~
"l is empty" 

Other examples

Enumerate a list

aya> ["A" "B"].enumerate :# {[i name], "$i: $name" :P};
0: A
1: B

Functional programming with recursion

aya> {[x xs~], xs [] = x {xs product x *} .? }:product;
aya> [1 2 3 4] product
24 

Haskell-style quicksort algorithm

{[x xs~ :l], 
    l [] = []
    {[ (xs.[{x:<}] quicksort)~ x (xs.[{x>}] quicksort)~ ]}
    .?
}:quicksort;

Recursive implementation of map operation

.# map
{[x xs~ :l] fn, 
    l [] = [] 
    {[(x fn) (xs fn.` map)~]}
    .?
}:map;