~technomancy/fennel#60: 
Support separate path lookup for macro modules

This is a proposal for a feature similar to the one we already have for ordinary code, but now for macros. I mostly write libraries, which often provide both functions and macros. Lua has a convenient feature, that allows importing libraries by requiring library directory, in which init.lua is located. Fennel support this feature and finds init.fnl in the same way.

However, for macros I have to provide some genericly named .fnl file, like macros.fnl and force user to look it up either in the docs, or in the sources.

It would be nice if we could create something like

library-with-macros/
├── init.fnl
└── init.fnlc

and then we would require this library as follows:

(local lib (require :library-with-macros)) ;; requires init.fnl
(import macros lib-macros :library-with-macros) ;; requires init.fnlc

Since #51 was implemented this sould be possible to do, if I understand this feature correctly.

This doesn't have to be init.fnlc, e.g. Fennel can enforce some naming on its own, like init-macros.fnl, but I think fnlc is good, as it can be read as fennelcompiletime or something like that.

Status
RESOLVED FIXED
Submitter
~andreyorst
Assigned to
No-one
Submitted
2 months ago
Updated
a month ago
Labels
enhancement macros

~technomancy a month ago

So the main motivation here is the desire to have two different modules (one runtime and one macro) that have the same name?

I'm not sure I understand what the benefit is. I think it's clearer if different modules always have different module names. Maybe I'm misunderstanding.

~andreyorst a month ago

So the main motivation here is the desire to have two different modules (one runtime and one macro) that have the same name?

Not that general. The motivation is to provide single entry point for the library.

For example, I have these three libraries: fennel-cljlib, fennel-conditions, and fennel-test. First two probide both run-time and compile-time modules, fennel-test only provides compile-time module.

User may want to require these libraries:

(local cljlib (require :fennnel-cljlib))
(local conditions (require :fennel-conditions)

Because both libraries provide init.fnl we can require those by requiring their directory.

But to require macro modules they have to look up the name:

(import-macros cljlib-m :fennel-cljlib.macros)
(import-macros conditions-m :fennel-conditions.macros)
(import-macros test :fennel-test.test) ;; oops main macro module is not macros.fnl but test.fnl due to history

I'm proposing the same feature we currently have for run-time modules with init.fnl to be available for macro modules as well. So users woul only have to know the name of the library directory:

(local cljlib (require :fennel-cljlib)
(import-macros cljlib-m :fennel-cljlib)
(local conditions (require :fennel-conditions)
(import-macros conditions-m :fennel-conditions)
(import-macros test :fennel-test)

This provides consistency, and single entry point.

Consecutively, if we allow both init.fnl and init.fnlc to be distinguished by require and require-macros, we can have runtime and comptime modules with same name, but this is not a main goal here. If you don't like this, you can enforce some other name that will act as init.fnl for macro modules, say init-macros.fnl.

~technomancy a month ago

The motivation is to provide single entry point for the library.

But you can't have a single entry point; you have to use both require and import-macros. You just have a single module name which maps to one module in one context and one module in another context.

On the other hand, we do have two separate "module namespaces"--we have package.loaded for runtime packages, and fennel.macro-loaded for macro modules. Loading a macro module is separate from loading a runtime module. They even have a separate list of searchers. So since there's a separate list of searchers, I think maybe it makes sense to introduce a separate fennel.macro-path which is used by the searchers.

I would be more inclined to use something like this as the default macro-path value:

"./?.fnl;./?/init-macros.fnl;./?/init.fnl"

Introducing an optional new file extension feels a bit odd, especially since .fnl still works for macros. So you can't look at foo.fnl and tell if it's a runtime or macro module. But having a separate path feels right, especially when we already have separate searchers.

And having one name that can resolve to two different modules isn't as weird as it first sounded, because the runtime and compile-time separation is really important, and it feels like maybe this actually helps emphasize that distinction.

~andreyorst a month ago

I would be more inclined to use something like this as the default macro-path value:

"./?.fnl;./?/init-macros.fnl;./?/init.fnl"

This seems good to me as well!

Additional thoughts - should searcher also support init_macros.fnl as well as init-macros.fnl? I don't know for sure, but I believe at least someone will be annoyed that they can't use snake case for file name which is the most common style AFAIK. init.fnlc is in better situation here, as it contains no word separators. Maybe something like macroinit.fnl?

~technomancy a month ago

Sometimes we include underscore versions of function names in Fennel APIs for the convenience of Lua users. But the reason dashes can be problematic in APIs is that Lua code can't conveniently access table fields containing dashes. That reason doesn't apply to module names; they are arbitrary strings rather than table fields. So the idea of putting underscores in a Fennel module name doesn't make a lot of sense to me.

~technomancy REPORTED FIXED a month ago

Implemented in fa24f0a.

Register here or Log in to comment, or comment via email.