Control Structures

Branching based on True or False

There is no "if" statement in Fexl to do branching. Instead, the boolean values T and F behave like functions which return either the first or second argument you give it. Although they are predefined in Fexl, they behave like this:

\T=(\x\y x)
\F=(\x\y y)

So for example if you want to say "YES" or "NO" depending on whether a value equals 0, you can do any of these:

(eq x 0) (say "YES") (say "NO")  # Fully parenthesized
eq x 0 (say "YES") (say "NO")    # No need for parentheses on the left.
say (eq x 0 "YES" "NO")          # Cleverly nest the decision inside the say.

For a more thorough example, consider the function which steps the so-called Hailstone Sequence:

\is_even=(\x eq 0 (mod x 2))

\step=
    (\x
    is_even x
        (/ x 2)
        (+ 1 (* x 3))
    )

\try=(\x say ["step "x" = "(step x)])

try 23
try 70
try 35
try 106

The output is:

step 23 = 70
step 70 = 35
step 35 = 106
step 106 = 53

Branching based on Empty or Non-Empty List

If you have a list in Fexl, the most common thing to do is determine whether the list is empty, or if it consists of an item followed by another list. Now sure, we could define the usual Lisp-like functions is_null, head, and tail, but what's the fun of that when we can instead treat data as functions in their own right, sort of like we did for T and F above, only more so?

As it turns out, Fexl lists do behave like functions, as if they were defined like this:

\null=(\do_null\do_pair do_null)
\cons=(\head\tail \do_null\do_pair do_pair head tail)

So when you're looking at an arbitrary list value, you pass in two handler functions do_null and do_pair, and the resulting value is either do_null if the list is null, or (do_pair head tail) if the list consists of head followed by tail. So for example:

\show=(\xs xs (say "null" nl) \x\xs say ["item " x])

# Show lists using conventional bracketed notation.
show []
show ["a" "b"]

# Show lists using a mix of null/cons and bracketed notation.
show null
show (cons 1; cons 2; [3 4])

The output is:

null

item a

null

item 1

Let's take a closer look at the show function there. It's written compactly without any unnecessary parentheses, but let's break it down a bit so we can see its logical structure. Let's put in all the parentheses.

\show=
    (\xs
    xs
        (say "null" nl)
        (\x\xs say ["item " x])
    )

Here you can easily see that we are taking the list value xs and applying it to two handler functions. The second handler function is given two values x, the first item of the list, and xs, the tail of the list.

Note that the tail uses the same name as the original list, which shadows the original definition. That is very common practice since usually once you've determined that the original list is a pair of a given head and tail, you no longer need to refer to the original list. However, I could have called the tail of the list tail to be more explicit.

Looping Using Fixpoint Recursion

Now that we're "diagnosing" list values, why not go ahead and write a function that prints each item in a list on a separate line? We need the show function to loop back on itself so it continues down the tail of the list. For that we use the fixpoint function, which is called @ to evoke the idea of a label, even though there is no "goto" involved whatsoever.

\show=
    (@\\loop\xs
    xs
        (say "null" nl)
        (\x\xs say ["item " x] loop xs)
    )

# Show lists using conventional bracketed notation.
show []
show ["a" "b"]

# Show lists using a mix of null/cons and bracketed notation.
show null
show (cons 1; cons 2; [3 4])

The output is:

null

item a
item b
null

null

item 1
item 2
item 3
item 4
null

I prefer to write list iteration functions more compactly, dispensing with unnecessary parentheses. I typically even put the pair handler parameters on the same line with the null handler. I would also probably put the loop call on a separate line. Also, you might prefer to call the recursion parameter show instead of loop.

\show=
    (@\\show\xs
    xs (say "null" nl) \x\xs
    say ["item " x]
    show xs
    )

That's pretty compact, yet clear when you get used to it, and there is no need for the individual "observer" functions is_null, head, and tail. Of course, you could define those observer functions if that's your style.

\is_null=(\xs xs T \_\_ F)
\head=(\xs xs void \x\_ x)
\tail=(\xs xs void \_\xs xs)

\show=
    (@\\show\xs
    is_null xs (say "null" nl);
    say ["item " (head xs)]
    show (tail xs)
    )