Remix.run Logo
uvas_pasas_per 6 days ago

Lisp is very cool. But one thing I do not like is that the syntax style `(fun a b c)` loses out to the less-symmetric "OO" style `a.fun(b, c)` when it comes to IDE auto-completion. And for me the IDE help is important to provide a smooth developer experience and minimize wasted brain energy. I'd like to find a way to make the editing and auto-completion work better. I think the way IDEs (and maybe our brains) work is: "here is a thing X, now what can I do to/with it?". That translates to typing in your editor: "X.[cursor here]", and then the IDE lets you see and search the things you can "do" with X. But if, in Lisp, you write "([cursor here]", you are immediately confronted with: "what is the signature of that function again?", but the IDE has nothing to go on. Maybe there is different style of editing, where we could type the arguments, and then trigger the auto-complete.

lenkite 6 days ago | parent | next [-]

In Clojure, this is merely (a/fun b c) or (. a fun b c) depending on whether a is a namespace or an object. Autocomplete works wonderfully. I would strongly urge you to try a coding session using Cursive so you can objectively refactor your obsolete LISP opinions.

bcrosby95 6 days ago | parent | prev | next [-]

I don't know about other lisps, but Clojure has namespaces and that's good enough for me. So you can type "(foobar/" and get a list of functions in the foobar namespace.

xigoi 6 days ago | parent | next [-]

Janet kind of has this too: if you import a module with (import foo), all the imported bindings will be prefixed with foo/. Also, this can be avoided by writing (use foo) instead.

tmtvl 5 days ago | parent | prev [-]

Common Lisp also has namespaces, you can type '(asdf:' and get the functions ASDF provides.

kqr 6 days ago | parent | prev | next [-]

Haskell sort of has this. You type (_ a b c) and the compiler tells you which methods would make sense there based on all three variables (so in a sense it is even more useful.)

uvas_pasas_per 6 days ago | parent [-]

Do you mean you run a build and it tells you in the error message? Or is there an IDE/editor where you could hit a key-combo with the cursor on that line, or near the `_`, and it would show a popup of possibilities for auto-completion?

kqr 6 days ago | parent [-]

Yes. Both are alternatives.

thegeekpirate 6 days ago | parent | prev | next [-]

Odin's language server calls it "fake methods".

https://odin-lang.org

https://github.com/DanielGavin/ols

everforward 5 days ago | parent | prev | next [-]

I think this is more familiarity bias and the presence of an enforced type system than a necessity of auto-complete. In the same way that `a.` auto-completes to methods of `a`, `(fun ` could auto-complete to a list of in-scope variables that satisfy `fun`'s type signature. A lot of lisp is untyped though, even in variants that have support for it.

I also think there's some familiarity bias to the OO style. I don't find it particularly natural, though that's subjective. I often know what I want to do, and have to find the object that has the method. E.g. I know I want to read a particular header, but is it on Request, or on Request.Headers, or are headers passed in as a separate object? It feels cleaner to do something like `(get-header "SOME-HEADER" ` and have the IDE tell me I need to pass in `(get 'headers request)` or similar.

tmtvl 6 days ago | parent | prev | next [-]

What do you do if 'a.<tab>' doesn't work because it's not 'a.fun(b, c)' but 'b.fun(a, c)' or 'c.fun(a, b)'?

Also think of autocompletion for 'var x = new' to 'var x = new ByteArenaFactory()', the way you get from nothing to 'ByteArenaFactory' is the same way you get from nothing to '(fun a b c)'.

xigoi 6 days ago | parent | prev [-]

Janet, like some other lisps, has the arrow macro, which allows you to write in the “OO” style.

  (-> a (fun b c))
zelphirkalt 5 days ago | parent | next [-]

Ah neat! In Guile I use threading/pipelining all the time with a small macro:

    (define-syntax ->
      (syntax-rules ()
        ;; first expression is left unchanged
        [(-> expr) expr]
        ;; take from the back, wrap other calls
        [(-> expr* ... (op args* ...))
         (op args* ... (-> expr* ...))]
        ;; make parens unnecessary in trivial case of no further arguments
        [(-> expr* ... op)
         (op (-> expr* ...))]))
Janet already having this ... Reading many good things about Janet in this discussion.
uvas_pasas_per 6 days ago | parent | prev | next [-]

Something like this combined with some IDE smarts to offer up the possibilities for `fun` after I type `(-> a` ... that could be very cool.

terminalbraid 6 days ago | parent [-]

You mentioned autocompletion a few times and that is something available via LSPs (janet specifically has one that has this) or through the editor (e.g. emacs can use swank for common lisp). It's no different for any other language integration.

worthless-trash 5 days ago | parent | prev [-]

I think that threading macros is different to method chaining (at least to my understanding), method chaining works on the object returned, and threading macros can work on raw data or objects.