Grazfather / dotfiles

39 stars 5 forks source link

Fennel lua escape hatch #2

Open harrygallagher4 opened 2 years ago

harrygallagher4 commented 2 years ago

Hi, I replied to your aniseed issue about (lua ...), since that issue is closed I thought I'd post this here. While you can't use (lua "return ...") at the top level of a fennel file, you can use it inside of a (when ...) and achieve your goal.

(local (ok telescope) (pcall require :telescope))

(when (not ok)
  (lua "return nil"))

(print :test)

{: telescope}

~Although, now that I'm writing this out, this technique will probably break aniseed modules... So perhaps this isn't that helpful...~

On the latest aniseed/develop this works fine

(module dotfiles.scratch.testmodule)

(local (ok telescope) (pcall require :non-existant-module))

(when (not ok)
  (lua "return nil"))

(print :test)
; requiring this module does not print anything, so code after the when expr
; really isn't being executed
Compiled ```lua local _2afile_2a = "fnl/dotfiles/scratch/testmodule.fnl" local _2amodule_name_2a = "dotfiles.scratch.testmodule" local _2amodule_2a do package.loaded[_2amodule_name_2a] = {} _2amodule_2a = package.loaded[_2amodule_name_2a] end local _2amodule_locals_2a do _2amodule_2a["aniseed/locals"] = {} _2amodule_locals_2a = (_2amodule_2a)["aniseed/locals"] end local ok, telescope = pcall(require, "non-existant-module") if not ok then return nil else end print("test") return _2amodule_2a ```

🙂

Grazfather commented 2 years ago

Right on! Thanks for tracking me down to show me this.

What I ultimately want is a macro that turns that into a one liner. Unfortunately it seems a bit tricky 'cause I both want to run a few expressions (e.g. in a do) but also to set a local in the module scope. That still gives me something to work with, though, so thanks again.

harrygallagher4 commented 2 years ago

Ah, gotcha. I'm not sure you'll be able to achieve that, sadly. Fennel macros can only return one form so you can't really do multiple things without using do and thus creating a new scope. However, if you just want to consolidate the pcall stuff, I did write a macro that makes that a little friendlier.

(require-hardcore-version [telescope :telescope]
                          [idk telescope.thing] 
  (print idk)))                                 
; -->
(let [(ok_1_ telescope) (pcall require :telescope)]
  (when ok_1_
    (let [idk telescope.thing]
      (print idk))))

(macro require-hardcore-version [mod-binding body-bindings body]
  (let [[mod-sym mod-name] mod-binding
        ok (gensym :ok)]
    `(let [,(list ok mod-sym) (pcall require ,mod-name)]
       (when ,ok
         (let ,body-bindings ,body)))))

Also, I think a good pattern for this would be to do the dependency checking for all modules in one place so you could just write a normal aniseed module that is only ever required if its dependencies are available. i.e.

(fn check-dep [d] (let [ok? (pcall require d)] (when ok? 1)))
(fn load-if-dep-available! [mod ds]
  (let [deps (if (table? ds) ds [ds])
        results (map check-dep deps)]
    (when (= (count deps) (count results))
      (require mod))))
; map, count, and table? are from aniseed.core

(load-if-dep-available! :dotfiles.module.telescope :telescope)
(load-if-dep-available! :dotfiles.module.something [:fennel :io])

No macros necessary.

Sorry if I'm getting a little over-involved in your dotfiles lol, I just found the problem interesting and have been tinkering with macros/aniseed's compiler so this was somewhat related.

Grazfather commented 2 years ago

I already have a call-module-setup that works pretty well. The issue comes up e.g. in my completion.fnl because the call to setup also requires 3 extra modules.

load-if-dep-available! is nice, but I am not sure if I want to have to codify all the deps both in the module as well as in the importer, especially if in the module itself I can maybe get it to mostly work (e.g. by setting up cmp without the missing extra modules).