Last active 6 months ago


Last active 9 months ago


Last active 10 months ago

#51 An equivalent of package.searchers for compile time 2 years ago

Ticket created by ~ioiojo on ~technomancy/fennel

This is a tracking issue for a more sophisticated method of loading Fennel macros at compile time.

One possible use for this is to expose Fennel macros to end users of an application which embeds Fennel modules, including user-facing macros. As of today, this can only be done by hand via running fennel.eval with compile scope, e.g.

(local fennel (require :fennel))
(local compiler (require :fennel.compiler))

(local str (with-open [file (io.open :path/to/macros.fnl :r)]
             (file:read :a)))
(local opts {:scope compiler.scopes.compiler})

(fennel.eval str opts)

#17 whole-table destructuring 2 years ago

Comment by ~ioiojo on ~technomancy/fennel

(match [:x :y :z]
  ([:x :y z &as xyz] ? (> z 1)) xyz)
(match [:x :y :z]
  ((@as xyz [:x :y z]) ? (> z 1)) xyz)
(match [:x :y :z]
  ([:x :y z] @as xyz ? (> z 1)) xyz)
(match [:x :y :z]
  ([:x :y z] @alias xyz ? (> z 1)) xyz)

I've noticed all the proposed approaches do some amount of mental overloading. Compared to prefix @as, the intra-table &as avoids extra parens which require visual unwrapping. Compared to postfix @alias or @as, intra-table &as doesn't risk stacking extra syntax (@as xyz ? expr).

After much internal debate, I'm joining the &as camp. Although I love Elixir's operator-based approach, intra-table &as may be the best way to express whole-table destructuring in a lisp family language.

#17 whole-table destructuring 2 years ago

Comment by ~ioiojo on ~technomancy/fennel

it forces the mind to break out of the context of what it's reading

Very salient point. And well said.

(let [[a b c &as whole-vec] [1 2 3]]

In this example, it does bother me how the whole-vec binding applies one level up from a, b and c. This forces a context switch. A subtle context switch, perhaps, but a context switch nonetheless.

Personally, I like how Elixir does whole-table destructuring:

def hi({:a, a} = yo), do: yo

Elixir's approach is what attracted me to the idea of making the whole-table destructure an operator, e.g. from:

def hi({:a, a} = yo), do: yo


def hi(yo = {:a, a}), do: yo


(fn hi [= yo {:a a}] yo)


(fn hi [(@as yo {:a a})] yo)

(or pick some other operator)

(fn hi [(:: yo {:a a})] yo)

Personally I like a pure operator sans alphanumeric chars here to minimize mental load.

#17 whole-table destructuring 2 years ago

Comment by ~ioiojo on ~technomancy/fennel

This is the only downside I can see. Compared with the downsides of the other proposals so far, it seems like a small price to pay.

I've only written a modest amount of Fennel, but I did not ever think to use & — or &as, nor anything else prefixed with & — as an identifier until just now.

I also prefer ~andreyorst's proposed syntax

(let [[a b c & rest &as whole-vec] [1 2 3 4 5]])
{:a a :b b &as whole}


(let [@as whole-table {: key1 : key2} (foo)] ...)

I especially like how &as complements the & operator.

#20 Calling metatable operators with varargs 2 years ago

Comment by ~ioiojo on ~technomancy/fennel

Very much agreed on the lisp syntax being a good thing here. I saw this crop up in my code and just had to wonder — but curiousity satisfied. I enjoy the simplicity of Lua combined with homoiconicity. Fennel for great good. Thanks for taking the time.

#17 whole-table destructuring 2 years ago

Comment by ~ioiojo on ~technomancy/fennel

The other thing is that we are probably going to need to introduce new syntax for reader macros anyway for other purposes, so if we can piggy-back on that, we only need to introduce one new syntactic construct.

Ah, k.

The main downside here is that whole-table is not as "visible" in the code; it's a constituent of a larger symbol. Right now all locals that are introduced are visible as standalone symbols in the binding form that introduces them; this would be an exception to that rule.

Would you be so kind as to provide psuedo-code which demonstrates what you mean when you say the whole-table destructure binding wouldn't be as visible in the code?

#20 Calling metatable operators with varargs 2 years ago

Ticket created by ~ioiojo on ~technomancy/fennel

(local foo {})

(fn foo.concat [...]
  (let [args [...]
        res []]
    (each [_ arg (ipairs args)]
      (table.insert res arg))

(fn foo.new [self init]
  (setmetatable {:x init} {:__index self
                           :__concat foo.concat}))

(let [a (foo:new 3)
      b (foo:new 6)
      c (foo:new 9)
      d (foo:new 1)]
  (print (fennelview (foo.concat a b c d)))
  (print (fennelview (.. a b c d))))

;; foo.concat →
[{ :x 3 }
 { :x 6 }
 { :x 9 }
 { :x 1 }]

;; .. →
[{ :x 3 }
 [{ :x 6 }
  [{ :x 9 }
   { :x 1 }]]]

I suspect this discrepancy in output has to do with Lua metamethods having infix notation, e.g. (.. a b c d) effectively transpiles to (((a .. b) .. c) .. d). IMO it would feel more lispy if the (foo.concat a b c d) and (.. a b c d) produced identical results. Wasn't exactly sure how to go about munging through Fennel's source tree for answers. And figured maybe it's best to assume metamethod overrides can only take two args. Can (should?) something be done about this?

#17 whole-table destructuring 2 years ago

Comment by ~ioiojo on ~technomancy/fennel

I like the idea of @as and the reader macro. Assuming I'm understanding it right, whole-table is the variable name and is arbitrary, e.g.

(let [@as/bar{: key1 : key2} (foo)] (. bar key1))

That seems good enough to me syntax wise.

Implementation wise, however, if it is a kludge on the language as you say, then IDK. I couldn't comment on the tradeoffs. Could making :: a reserved symbol be less intrusive on the language than a reader macro?

#17 whole-table destructuring 2 years ago

on ~technomancy/fennel


#17 whole-table destructuring 2 years ago

Comment by ~ioiojo on ~technomancy/fennel

That's a cool approach. I like it. And certainly special casing that not= :: ":" comes with purity tradeoffs. JW but is it feasible for the @as to go outside the table?

(fn abc [({:abc abc} @as full-table)] ...)

Thought I'd throw in moar parens for great good ... heh.

Fun detour: I got the idea for :: from Raku.

# unpunned hash entry notation
my %h = a => 10, b => 20;

# punned
my %h = :a(10), :b(20);

# illegal punning
my %h = ::(10);

Raku limits what can be punned, e.g. :a(1) == "a" => 1, but :: is reserved for object attribute access.