(λ (x) (create x) '(knowledge))

Fennel Confusions

Sorting Through Some Personal Confusion · May 18, 2020

From time to time I get a little lost trying to translate Lua to Fennel, and while the language resource is an absolute blessing to have, sometimes I need things spelled out for me explicitly. I'm probably not the only person that feels that way, and can spend hours digging through other people's code for a snippet that points them in the right direction on some obscure syntax issue, or library integration, so maybe this post will help someone out in the future.

My latest confusion was over something dreadfully simple. In Lua there exists multi-level checking for if statements, in an incredibly simple syntax, there's a great directory checking snippet I found which shows this off perfectly:

function is_dir (path) local f = io.open(path, "r") return not f:read(0) and f:seek("end") ~= 0 end

Really simple function, if we cannot read the end of the path, and the file size is not 0, then we return true, otherwise the statement is false. But how would we translate that to Fennel? I at first thought something like this made sense

(fn is_dir [path] (local f (io.open path "r")) (if (~= (not (f:read 0) and (f:seek "end")) 0) true false))

This kind of made sense in my mind, as I had assumed that in Fennel the if macro worked a little bit like the loop macro in common lisp. That is to say that I assumed it had a DSL built into it to handle the domain of conditional statements. This is unfortunately not the correct assumption and that snippet above won't even return anything useful when run through the Fennel compiler. If we stop and think about it for a second, the solution is much more lispy than what I came up with.

(fn is_dir [path] (local f (io.open pkg "r")) (if (not (f:read 0)) (and (~= (f:seek "end") 0)) true false))

Technomancy made the following observation after reading this post, which clears things up nicely. The if statement mapping is pretty clearly defined between Fennel and Lua.

(if (not (f:read 0)) ; <- condition (and (~= (f:seek "end") 0)) ; <- result true ; <- elseif condition false ; <- elseif result )

This snippet is the correct syntax for this type of logic test. This is because we have two separate tests in the original Lua snippet, each needs to be encapsulated in its own function in the conditional operator. This is pretty close to how you'd do it in Common Lisp, which I personally really like.

(if (and (not (= f nil)) (not (= fsize 0)))

The only real difference, and I didn't come to it initially, is that the and macro in lisp encapsulates a set of conditionals to be equated together, where as in Fennel it's more so a compiler call that denotes a translation. When we call the and macro it compilers like: (and (~= (f:seek "end") 0)) -> and f:seek("end") ~= 0, whereas in CL we're compiling down to bytecode, a whole other beast, or rather to say, the translation can be a less literal translation between the two languages. (Or so I guess, I would be a fool if I pretended to understand the inner working of a lisp implementation on that level.)

In a final note, thank you to anyone actually reading these blog posts. I got a little bit of feedback on toAPK over IRC, and it was incredibly motivating. If you've taken the time to read through any of this, or look at some of the projects I'm working on, I really appreciate it!