Factor pt. 4 - Vocabularies
Tags: esoteric, concatenative, factor, tutorial
Published: 2021-10-16 11:41:59

Over the last three tutorials I dumped a lot of information at you, explained it semi-coherently and only gave a couple practical examples. Now is the time to fix this all at once: this article is all about a big practical example that will touch on everything I have already covered and expand on it a bit. Today we are writing our own Factor vocabulary.

### What is a vocabulary?
###### A bit of theory before the practice
In most languages only the most essential part of available functionality is present by default and if you need any specialised functions or objects you need to `import` or `#include` or whatever your favourite language does to load features from an external file. Most languages call those extensions "libraries" or "packages", but because in Factor everything is a word these files are just lists of words: vocabularies.

If you followed the examples I gave on your computer you may have already used vocabularies. Whenever the Listener encounters a word that is in an unloaded vocabulary it will throw an error and present options, one of which will be to use the library including that word. It's useful to not load all libraries by default because it would take up a lot of space to compile all Factor libraries, and some of them use conflicting word names (for example `range` is in `math.ranges`, `math.statistics` and `model.range`).

You can order Factor to load vocabularies with the words `USE:` and `USING:`. Their syntax is as follows:
```Factor
USE: vocab-name
USING: vocab-names... ;
```
Usually a Factor program will have a big `USING:` line at its start that loads all the vocabularies it needs. Words you define in the Listener go into the `scratchpad` vocabulary which exists as long as the Listener is open.

### Creating the vocabulary
###### Let's get down to business
A user created vocabulary usually sits in the "work" folder in your Factor installation directory. Vocabularies are stored in plaintext source files so in theory you could just find the folder and create the required structure yourself, but Factor conveniently has a vocabulary for defining vocabularies so it can do the hard work for us.

Before we do anything we need to `USE: tools.scaffold` because this vocabulary will not be automatically suggested by the Listener. I will be making a library-managing vocabulary, so I will call it "esoposting-library". To scaffold a library push it's name as a string onto the stack and call `scaffold-work`. Now you can do it again, but call `edit` instead and we will be ready to start.

In summary this is what you should type into the listener to create and edit your vocabulary:
```Factor
USE: tools.scaffold
"esoposting-library" scaffold-work
"esoposting-library" edit
```
At this point Factor may ask you for your editor of choice which should launch immediately, provided it is installed. The initialised file should look like this:
```Factor
! Copyright (C) 2021 Your name.
! See http://factorcode.org/license.txt for BSD license.
USING: ;
IN: esoposting-library
```
Feel free to substitute your actual name for "Your name" in the header.


### Designing the vocabulary
###### Design first, code second.
Before coding it's good to have an idea what the code is for and how it should work. My idea is a program to keep track of my personal library. It should support adding new books and removing old ones, searching for the book by title, and borrowing books: keeping track of whether a book is currently borrowed and to whom and how many times it has been borrowed. I should also be able to see a list of books I own in alphabetical order.

A more serious approach would look at Factor's database support, but I'll settle on a data structure holding the books that will sit atop the stack. Book management words will act on that object and if I choose the structure well they should mostly turn out to be aliases of already existing words. Because I want the books to be searchable by title I will implement the library with a hashtable.

### Classes
###### What objects do we need?
From the problem specification two object types are apparent: books and the library containing them. Let's start from the simpler one.

#### Book
What information should a book carry? From what was explained earlier we need to know a book's title, how many times it was borrowed, is it borrowed now and if so then who borrowed it. In addition to that I want know the book's author and genre. That would give us 6 fields for class `book`.

Not all of these have to be slots of the book tuple though. Info on whether the book is borrowed and by whom could be stored by having a false value when the book is in the library and a string with the borrower's name if it's out. That is actually its own type of data called "maybe string". Additionally the title will be stored as a key to the library hashtable, but I want the redundancy of keeping it in the tuple as well. That way every word that uses a book can automatically use its title.

The final `book` tuple class declaration looks like this:
```Factor
USING: classes.maybe math strings ;
TUPLE: book
    { title string read-only }
    { author string read-only }
    { genre string read-only }
    { times-borrowed integer }
    { borrower maybe{ string } }
;
```
A "maybe string" class in Factor is written as `maybe{ string }` and that's the type of the `borrower` slot. Also note that we already need to use some vocabularies. All of them should be gathered in the third line of the generated vocabulary header. To check if everything works correctly call `"esoposting-library" reload` in the listener.

#### Library
The library object doesn't need to be anything more than a collection of books searchable by the title. We can get away with not implementing a separate class for this purpose because a hashtable does exactly what we want. A drawback of not declaring a new class is that words we define for working with the library will also attempt to work with other objects if used incorrectly which can lead to hidden bugs and unhelpful error messages.

In our case the project is too small for that to matter. If our vocabulary was supposed to be a part of some bigger application it would be a good practice to define a `library` class tuple, but it would be an overkill for this purpose.

### Words
###### A top-down approach
I have already mentioned in a previous article that when writing code I like to start with the top level words I need and see what other words will be necessary to implement them. For our application there will be a few of those.

#### Creating the library
First the user needs to be able to `init-library` to get the library object and not need to care about how it's implemented:
```Factor
USE: kernel
: init-library ( -- library )
    H{ } clone
;
```
`H{ }` is syntax for a default empty hashtable, and `clone` makes sure every call to `init-library` creates a new hashtable and not just a reference to the same one. It's a preferred way of initialising hashtables if you don't need to specify capacity.

Then an `add-new-book` word would be nice that would take a title, author and genre, create a new book and add it to the library:
```Factor
USE: accessors
: add-new-book ( library title author genre -- library )
    <book> swap over
    title>> swap
    [ set-at ] keep
;
```
To add a key-pair value to a hashtable you use the word `set-at` which stack effect is `( value key assoc -- )`. The first problem is that both value (the book) and key (the title) have to be below the assoc (the library hashtable), and this is dealt with by some stack shuffling. The second problem is that `set-at` consumes the hashtable, but it can be remedied by enclosing it in a quotation and calling via the `keep` combinator which saves the top value, executes the quotation and restores the saved value.

The third problem with `add-new-book` is that we didn't define the constructor `<book>` yet, so let's do that. It needs to take the initial data (title, author and genre), fill the default values of the other slots and use a `boa` constructor to make a `book`:
```Factor
: <book> ( title author genre -- book )
    0 f book boa
;
```

#### Accessing the library
After adding all the books to the library it would be nice to be able to interact with them. From what I specified in design we need words for searching, deleting and borrowing books. Let's also make them user friendly, so if I try to interact with a nonexistent book I get a warning message. For these I will use the `print` word from the `io` vocabulary which takes a string and displays it on the console.

To get a book from the library we need to pass it's title and the library itself to the generic word `at`:
```Factor
USE: io
: find-book ( library title -- library book )
    over at dup
    [ "WARNING: a book of this title was not found!" print ]
    unless
;
```
`at` will not throw any errors when the book is not found, it will simply return `f`. This means we can check the returned value and warn the user `unless` the book was found.

To delete a book from the library we need the same information as when searching passed to the generic word `delete-at`:
```Factor
: delete-book ( library title -- library )
    over at dup
    [ title>> over delete-at ]
    [ "WARNING: a book of this title was not found!" print drop ]
    if
;
```
Instead of simply deleting the book, which would work even if the book was not present, I check if the books exists beforehand so I can warn the user about it. The action they attempted was not carried out successfully, so they need to know that. Maybe they made a typo?

The final utility expected from this vocabulary is handling borrowed books. For this I will define two words: `borrow-book` and `return-book` that will adjust appropriate slots of the book's slot. In addition to that there are two new warnings: `borrow-book` should not work if the book is already borrowed, and the same for `return-book` and not borrowed books.
```Factor
: borrow-book ( library title borrower -- library )
    [ drop find-book ] keep over    ! Fetch the book and warn if isn't found
    [ over borrower>>               ! If is found check if already borrowed
        [ "WARNING: the book has already been borrowed!" print 2drop ]
        [ >>borrower [ 1 + ] change-times-borrowed drop ]
        if ]
    [ 2drop ] if                    ! If not just do nothing
;

: return-book ( library title -- library )
    over at dup             ! Fetch the book
    [ dup borrower>>        ! If found check if borrowed
        [ f >>borrower ]    ! If borrowed return
        [ "WARNING: the book is not borrowed!" print ]
        if ]
    [ "WARNING: a book of this title was not found!" print ]
    if drop
;
```
Now hold up. Remember the guidelines? Whenever a definition is big enough that comments are needed to explain what is going on it's a sign that you need to factor something out.

### Factorisation
###### The best practice of all
There is a big and obvious factor common to all words in our vocabulary: the "book not found" warning. It could be factored out using just `find-book`, but there is a better way. Let's define a combinator that will take the library, title and a quotation, call the quotation if the book is found and otherwise warn the user and do nothing:
```Factor
: if-book-exists ( library title quot: ( book -- x ) -- library x|0 )
    [ drop over at dup ] keep
    [ "WARNING: a book of a given title was not found!" print ]
    if
; inline
```
This word, like all combinators, is and `inline` word. It means that before compiling all occurrences of it will be replaced with its body. This lets the compiler handle stack effects more efficiently and is required for most words that take quotations as arguments.

Let's now rewrite previous words in terms of this new combinator. Most of them will have a form of `[ body ] if-book-exists`.
```Factor
: find-book ( library title -- library book )
    [ ] if-book-exists
;

: delete-book ( library title -- library )
    [ title>> over delete-at f ] if-book-exists
    drop
;

: return-book ( library title -- library )
    [ dup borrower>>
        [ f >>borrower ]
        [ "WARNING: the book is not borrowed!" print ]
    if ] if-book-exists drop
;
```

Returning the book takes an additional argument - borrower - but the body quotation's stack effect for `if-book-exists` must only take a book as an argument. To make this happen we need to construct a quotation that already contains the borrower. This brings us to the concept of partial application, or currying. It's a functional programming term which is most easily explained by example.

Addition is a function that takes two numbers and returns a number. But it can sometimes be useful to first supply it with only one argument and then use as a single argument function. For example if we know that a function that adds 5 to a number will be useful we can apply + to 5 getting `[ 5 + ]` which still requires one argument to return a number. This can be done with any function, producing a new function that takes one less argument and in Factor currying is achieved with the word `curry`.
```Factor
value [ body ] curry
! S: [ value body ]
```
Now we need to make a quotation with stack effect `( book borrower -- x )` and partially apply it to the borrower producing a quotation with stack effect `( book -- x )` which is what we need for `if-book-exists`.
```Factor
: borrow-book ( library title borrower -- library )
    [ over borrower>>
        [ drop "WARNING: the book has already been borrowed!" print ]
        [ >>borrower [ 1 + ] change-times-borrowed ]
    if ] curry if-book-exists drop
;
```

### Final code
###### Works for me
This is how the "esoposting-library.factor" file looks like after all the words have been added:
```Factor
! Copyright (C) 2021 Aleksander Sabak.
! See http://factorcode.org/license.txt for BSD license.
USING: accessors assocs classes.maybe io kernel math strings ;
IN: esoposting-library

TUPLE: book
    { title string read-only }
    { author string read-only }
    { genre string read-only }
    { times-borrowed integer }
    { borrower maybe{ string } }
;

: init-library ( -- library )
    H{ } clone
;

: <book> ( title author genre -- book )
    0 f book boa
;

: add-new-book ( library title author genre -- library )
    <book> swap over
    title>> swap
    [ set-at ] keep
;

: if-book-exists ( library title quot: ( book -- x ) -- library x|0 )
    [ drop over at dup ] keep
    [ "WARNING: a book of a given title was not found!" print ]
    if
; inline

: find-book ( library title -- library book )
    [ ] if-book-exists
;

: delete-book ( library title -- library )
    [ title>> over delete-at f ] if-book-exists
    drop
;

: borrow-book ( library title borrower -- library )
    [ over borrower>>
        [ drop "WARNING: the book has already been borrowed!" print ]
        [ >>borrower [ 1 + ] change-times-borrowed ]
    if ] curry if-book-exists drop
;

: return-book ( library title -- library )
    [ dup borrower>>
        [ f >>borrower ]
        [ "WARNING: the book is not borrowed!" print ]
    if ] if-book-exists drop
;
```
All libraries I am `USING:` are specified at the top in alphabetical order. I encourage you to try and play around with this vocabulary or whatever you came up with during this tutorial. Remember that you can prettyprint whatever is on the stack with `.`, even tuples.

### Conclusion
###### That was a lot of writing
I hope you enjoyed this more practical tutorial. You are now ready to go and experiment with writing your own vocabularies. As always remember to make use of the built in help, there is a lot of knowledge to be found in the docs. This is the end of the first chapter of this series, next time I will discuss Factor's GUI vocabularies and start building towards making a desktop application.