Skip to content

The (.) macro's design is broken #13

@tabatkins

Description

@tabatkins

In the README, you imply that (. a 3) compiles to a[3] (good), but then state that (. a b) compiles to a.b (in other words, for all but the first argument atoms are converted to symbols/strings). This means it's impossible to access into a data structure with a variable; there's no way to write the equivalent of JS's a[b].

To do this properly, named property access needs to always be done with strings, like (. a 'b'), or at least with self-evaluating symbols, like (. a :b). This way everything that looks like variables is actually variables.

Activity

anko

anko commented on Sep 23, 2015

@anko
Owner

The computed member expression macro is called get.

(get a b)a[b];
(. a b)a.b;

It's mentioned toward the end of the tutorial section about arrays and objects. I realise the distinction can be confusing though, and the docs should communicate it better.

anko

anko commented on Sep 23, 2015

@anko
Owner

To do this properly, named property access needs to always be done with strings, like (. a 'b'), or at least with self-evaluating symbols, like (. a :b). This way everything that looks like variables is actually variables.

In JavaScript, a.b does the same as a["b"], so I think it's logical that (. a b) and (. a "b") should do the same also—right?

tabatkins

tabatkins commented on Sep 23, 2015

@tabatkins
Author

Ah, kk. I disagree with the design, but at least it can be worked around.

In JavaScript, a.b does the same as a["b"], so I think it's logical that (. a b) and (. a "b") should do the same also—right?

The difference is that in a.b the "b" part syntactically cannot be a variable, and you quickly learn the difference between a.foo and a[foo] (along with what other things can't be expressed via dot notation, like a[3] or a[b+"foo"]).

The (.) macro, though, eliminates the syntax distinction. You can put whatever literal value you want in there, whether it matches the JS identififer syntax or not - (. a "foo") and (. a 3) are equally valid. It is a general principle that you can always replace a literal with a variable containing that literal, but here you can't - switching to (block (= b "foo") (. a b)) does the wrong thing.

Similarly, if I assume that (. a (+ b "foo")) is valid (it looks like it is, per the (. a b (. c d)) example in the docs), then you further violate expectations when a seemingly-trivial edit - removing the concat - drastically changes which property is accessed (it switches from retrieving the value of b and concatting "foo" to it, to just retrieving the "b" property).

It's common in Lisp APIs to use self-evaluating symbols (:foo) when you want "lightweight strings" in some API. This is why I suggested (. a :b) as the syntax to desugar to a.b. Self-evaluating symbols are detectable at parse-time (in Lisps they're implemented via a reader macro), so it should hopefully be reasonably simple with your approach.

(Another inconsistency in the current approach - (. a b) desugars to a.b, but (. a foo-bar) cannot desugar to a.foo-bar, as that means something totally different in JS. You might restrict dashes in variable names, I'm not sure, but if you don't, this already needs to desugar differently, to a["foo-bar"], so the direct-mapping metaphor breaks down even for relatively simple cases.)

anko

anko commented on Sep 24, 2015

@anko
Owner

Thanks so much for your thoughtful analysis. I think I see what you mean now.

What you suggest is that if the . macro behaved such that identifiers in it always implied computed member expressions (e.g. (. a b)a[b]) and strings always non-computed member expressions (e.g. (. a "b")a.b), then we could discard the get macro altogether and eliminate this confusion.

Test cases:

(> (. a b) 1)a[b] > 1
(> (. a "b") 1)a.b > 1 (or equivalently, a['b'] > 1)
(. a (+ b "foo"))a[b + "foo]

I think it's a great idea.

I could implement it tomorrow. Can I ping you for a review?


Eslisp checks the final AST being compiled to JS for illegal identifier names (e.g. foo-bar) and logs errors if it finds them, so they are no problem.

If you prefer to use dash-separated variables, eslisp-camelify can automatically turn them into camelCase. This is pretty convenient with the --transform flag that wraps the whole program in a macro, as used e.g. here.

tabatkins

tabatkins commented on Sep 24, 2015

@tabatkins
Author

Yeah, I'm happy to do a review. ^_^

added 2 commits that reference this issue on Sep 24, 2015
74f08c1
a0fa525
Gonzih

Gonzih commented on Sep 27, 2015

@Gonzih

Why not use . for method invogation and get for property access? Previous and new . semantics feel very confusing to me.

anko

anko commented on Sep 27, 2015

@anko
Owner

@Gonzih I don't understand. Could you give an example?

Gonzih

Gonzih commented on Sep 27, 2015

@Gonzih

It might be a bit far from current implementation and ideas. Also might be influenced by clojure heavily.

What I don't like is that property access and function invocation are separate things.

It's to verbose to get function and then invoke it. For me it feels much more stricts to use get to access properties, but (. a b c) would alsay compile to a.b(c). But when I think about it it might be different from original design :)

anko

anko commented on Sep 27, 2015

@anko
Owner

My idea is to keep the core language very plain (so e.g. . means exactly a member expression, nothing else), then let user-defined macros add sugar, and make user macros as easy as possible to write and start using.

(. a b c)a.b(c); sounds like it would work great as a user macro though. The eslisp-fancy-function macro is an analogous thing for the function macro. (If you want to write one and want tips or get stuck, do ask. 😄)

Gonzih

Gonzih commented on Sep 27, 2015

@Gonzih

ok, I understand. I will try to play with user defined macros for my needs. Thanks!

24 remaining items

reopened this on Oct 9, 2015
dead-claudia

dead-claudia commented on Oct 13, 2015

@dead-claudia
Contributor

Suggestion for this, inspired by my suggestion in #23: make static properties (. obj 'foo) and computed properties (. obj foo) or (. obj (func)). Does that sound like a good resolution for this?

anko

anko commented on Oct 22, 2015

@anko
Owner

@IMPinball Yep, that's the plan; @lhorie outlined it similarly before too.

The issue blocking me from doing this right away is that currently 'foo compiles to the wrong value when used anywhere other than as a macro return value: 'foo({ atom: 'foo' });. This in turn is because user macros and built-in macros actually use different formats for representing the S-expression tree.

I'll hopefully soon have more time and attention to focus on it.

added and removed on Oct 22, 2015
dead-claudia

dead-claudia commented on Dec 26, 2015

@dead-claudia
Contributor

@anko I think the difficulty is that sexpr-plus aliases 'foo as (quote foo) and returns that result, instead of combining the two. We'd have to create a new node type for sexpr-plus to fix that problem (it's bad behavior IMHO, but it'd require a major version bump as sexpr-plus is already past 1.0). Meanwhile, I'm tentatively implementing @lhorie's syntax.

dead-claudia

dead-claudia commented on Dec 26, 2015

@dead-claudia
Contributor

My tentative implementation is coming in a PR I'm currently working on, which will fix both this and #23.

iovdin

iovdin commented on Mar 25, 2017

@iovdin

i'd expect (. (foo) (bar)) to be translated to foo().bar()
which actually translates to foo()[bar()]

vendethiel

vendethiel commented on Mar 25, 2017

@vendethiel

You want ((. (foo) "bar")).

iovdin

iovdin commented on Mar 25, 2017

@iovdin

@vendethiel right,
but for long chains which happens quite often it becomes not that readable
(. (foo) (bar) (baz)) i have to write ((. ((. (foo) "bar") "baz)))

vendethiel

vendethiel commented on Mar 25, 2017

@vendethiel
iovdin

iovdin commented on Mar 26, 2017

@iovdin
added this to the v1 milestone on Jul 26, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    Relationships

    None yet

      Development

      No branches or pull requests

        Participants

        @whacked@vendethiel@Gonzih@iovdin@tabatkins

        Issue actions

          The (.) macro's design is broken · Issue #13 · anko/eslisp