fif - Standing on the Shoulder of Giants

Introduction

This is the second installment in a series of blog posts that introduce fif, a stack-oriented scripting language written in clojure.

The first blog post outlines self-discovery using the repl, allowed EDN values, defining word definitions, and touches on word referencing. This blog post will start to cover more of the difficult to comprehend parts.

Your condition? What is?

The strangest thing I had to grok about stack-orientation is how conditional structures appear to be backwards. In order to determine a branching path to take, you need to present a boolean condition on the stack. The if conditional then takes this boolean condition from the top value on the stack and uses this value to determine how it should branch. That being said, here is the syntax for the if conditional:

<boolean flag> if <truthy statements..> [else <falsy statements..>] then

The boolean flag represents a value that is currently on the stack, and this in turn triggers the truthy statement when it is true. But what determines if this value is true? A boolean flag is true if:

  • It is a number not equal to 0
  • If the clojure statement (boolean <boolean flag>) is true.

The boolean function in clojure loves everyone and everything, and treats only false and nil as false conditions. This is important to remember!

You'll also note that the if conditional accepts an optional else statement to present a falsy statement.

Let's test out some of these ideas in the interactive repl:

42 if "Not equal to zero" then

_ 0 if "I'm so alone" then

_ 0 if "Something is wrong" else "Value is equal to zero" then

Let's work the clojure boolean function

true if "Yeah" then

_ true true? if "Yeah, true" then

_ false if "Yeah" else "Nah" then

_ false false? if "Yeah, false" then

Clojure boolean loves everyone, but nil and false

[] if "Oh mah gosh! I like your vector!" prn then

_ () if "That's such a beautiful list!" prn then

_ {} if "How many sets is that?" prn then

_ "" if "Have you been working out?" prn then

_ nil if "My Pog Collection is better than your Pog Collection!" prn then

Loop-di Whoop, Loop-Diddy-Whoop

Looping in fif is pretty straightforward, but again, things can appear backwards since it follows in the footsteps of other stack-oriented languages.

do-loop

The first and most used loop construct is the do loop, which is similar to the for loop in imperative languages.

<end> <start> do <body..> loop

The do loop requires a start and an end index to determine how many times it should loop. Note that the start and end indexes are inclusive, which means if you specify 5 0, it will perform 6 iterations.

5 0 do "Hello!" loop

_ "Loop performed '" . $get-stack count . "' Iterations" .

It would be useful if you could also know what iteration you're on. Fortunately, fif has index word definitions which retrieve the current index from the return stack. These word functions are named i, j, and k.

5 0 do i 5 = if "fif!" else i then println loop

_

Indexing beyond i particularly shines in nested loops

2 0 do 3 0 do

j i pair

loop loop

_

The do loop also includes a variation on the loop to increment at a different step. The syntax of this variation is:

<end> <start> do <body..> <step> +loop

Each iteration of the loop will increment the index by the provided step. The last value could span past the end index as seen in the example.

10 0 do i 3 +loop

_

begin-until

One of the more versatile loops is the begin-until loop, which is defined as:

begin <body..> <flag> until

The begin-until loop performs each iteration until the conditional flag is true. The flag follows the same conditional true and conditional false rules as the if conditional. Remember, boolean loves you.

;; Fizzled begin-until, note that the first iteration is always called.

begin "Loop!" true until "We're done here"

_

Iterating over a variable might be a more interesting example:

def iter 5

def iter-end 10

_ begin

_ ;; Print Iteration Message

"Current Iteration: " . iter println

_ ;; Increment Iterator

*iter iter inc setg

_ iter iter-end = until

begin-while-repeat

The begin-while-repeat loop condition will look familiar. The syntax is:

begin <flag> while <body..> repeat

The flag is checked first, and if the boolean flag is true, then an iteration of the body is executed. Remember, boolean loves you.

;; Conditional is checked first before iteration

begin cake-is-a-lie? while "Loop!" repeat "We're done here"

_ ;; Note that cake-is-a-lie? is always false, obviously.

We're working in a stack, so let's do some stacky stuff.

0 begin dup 5 not= while dup inc repeat

_

In this case, we're using the stack value to determine when our loop should exit. How neat is that?!

High-functioning Computer Love

I personally am not a big fan of conditional loops. Fortunately, fif has functional programming built-in.

reduce

Everything stems from reduce, so let's look at its syntax:

<fn ( x1 x2 -- x' )> <coll> reduce

Reduce takes a word function as its first argument, which can be passed onto the stack using Word Referencing. The second argument on the stack is the collection we wish to reduce.

*+ [1 2 3 4] reduce

_

In the example, we are passing the addition, +, word function onto the stack with a collection of numbers. The + word function takes two numbers and places the addition of these two numbers back onto the stack. This is then treated as the first argument (x1) in the next iteration, with the second argument (x2) being the next value in the collection. This repeats until the collection is depleted. This is the story of reduce.

map

map is probably the most used in functional programming:

<fn ( item -- 'item )> <coll> map

Map takes a word function as its first argument. The function in question needs to take a value off of the stack and replace it with a new value. The result is a transformed collection.

*inc [1 2 3 4] map

_

In the example, we are passing inc as the word function, which increments each value in the collection. The result is then returned as a list collection.

filter

If you have a collection with values that you want removed, filter is the way to go. The syntax:

<fn ( item -- boolean )> <coll> filter

Filter takes a word function as its first argument. The function returns a truthy or falsy value to determine if the value should be removed from the returned collection.

*even? [1 2 3 4 5] filter

_

In the example, the collected values are filtered by whether they are even. Values that don't make the cut are removed from the returned list collection.

That's it

With time, I plan on adding more higher-order word functions, but these three should make the cut for now.

What was discussed?

So far in this blog post, I have covered:

  • Conditional Structures
  • Conditional Loops
  • Functional Programming Word Functions

There's still so much more to talk about, but this blog post is getting lengthy. Let's save it for another post!

But I want it now!

If you would like to learn more about fif, and what it has to offer, the github page goes more in-depth into most of the standard library operators, and also explains how to extend and use fif in your clojure(script) projects.

There is also an online fif playground which contains scripts expressing the many things you can do in fif. I will be covering each of these in later blog posts.

Can you at least give me an example of its applications?

I have an overactive imagination, but one particular use case that would intrigue me would be a web console to communicate with its own cljs web application. This same console could be used to carry out tasks that might be difficult or not cost-effective to develop a UI for. An example of this idea can be seen on this very site:

Press the tilde key, or Click here.

Some interesting console commands to try:

my/info pprint
my/projects pprint
my/work-experience pprint
my/research-publications pprint

<url> image
<video-id> youtube
random-whale-fact println
never-gonna-give-you-up
fif-logo

The web console is a work-in-progress, but I hope it sparked your interest!

That's it, and thanks for reading

If you feel like I've missed something, or you wish to express your undying love for stacks, you can find me on the clojurians slack as @benzap.


Related Resources