The Awesome Factor

Magic Combinator

Thursday, July 11, 2024

Magic Combinator

In response to a blog post about Stack Complexity in Factor, I figured I would add one way that I structure programs when possible.

Suppose you want to make a server that runs sql queries where a route name is a filename. Some requirements of this are connecting to a database, opening a directory of sql templates, loading them all into a hashtable to query by filename, handling incoming connections, doing the query, writing the results back out–you obviously can’t reason about all of this state on the stack at the same time. It would be ten items deep, and we want to keep the stack depth to 3-4 things at once in the same stack effect. (Of course the stack keeps growing deeper, but we need to reason about only a few things at any time.)

You can imagine a magic combinator, composed of other combinators, that will do most of this for you. Suppose you start with a with-database combinator that will connect and tear down the connection and handle exceptions. Next, you need to read the templates–with-template-directory to the rescue! To handle all the incoming requests you will need a with-server combinator. We should also set up some routes that map to the filenames, maybe call it with-routes.

Now that we have all the parts, let’s combine them into a combinator that works with dynamic variables and you have your magic combinator!

! some setup variables
SYMBOL: db-settings   ! initial settings for db
SYMBOL: server-port   ! port

! state within the combinators
SYMBOL: db-connection
SYMBOL: templates
SYMBOL: routes
SYMBOL: client-connection

: with-networked-database-template-directory ( template-directory quot: ( args template -- ) -- )
    [ db-settings get ] 2dip '[
        _ [
             [
                 _ with-routes
            ] with-server
        ] with-template-directory
    ] with-database ; inline

Now your quot magically has the db connection, the templates, the routes, the client connection, and you can just concentrate on templating the query and getting the results, confident that this combinator takes care of the setup. You can access any of the state with code like db-connection get. If you need more state, just add another combinator. If there are problems, you can debug each combinator individually.

Hopefully this will help you handle state in your Factor programs, or even in python using the with keyword.