-
Notifications
You must be signed in to change notification settings - Fork 32
Open
Description
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.
Metadata
Metadata
Assignees
Labels
Projects
Milestone
Relationships
Development
Select code repository
Activity
anko commentedon Sep 23, 2015
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 commentedon Sep 23, 2015
In JavaScript,
a.b
does the same asa["b"]
, so I think it's logical that(. a b)
and(. a "b")
should do the same also—right?tabatkins commentedon Sep 23, 2015
Ah, kk. I disagree with the design, but at least it can be worked around.
The difference is that in
a.b
the "b" part syntactically cannot be a variable, and you quickly learn the difference betweena.foo
anda[foo]
(along with what other things can't be expressed via dot notation, likea[3]
ora[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 toa.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 toa.b
, but(. a foo-bar)
cannot desugar toa.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, toa["foo-bar"]
, so the direct-mapping metaphor breaks down even for relatively simple cases.)anko commentedon Sep 24, 2015
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 theget
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 commentedon Sep 24, 2015
Yeah, I'm happy to do a review. ^_^
Rework `.`-macro to support computed member exprs
Rework `.`-macro to support computed member exprs
.
-macro to support computed member exprs, removeget
. #14Gonzih commentedon Sep 27, 2015
Why not use
.
for method invogation andget
for property access? Previous and new.
semantics feel very confusing to me.anko commentedon Sep 27, 2015
@Gonzih I don't understand. Could you give an example?
Gonzih commentedon Sep 27, 2015
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 commentedon Sep 27, 2015
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 thefunction
macro. (If you want to write one and want tips or get stuck, do ask. 😄)Gonzih commentedon Sep 27, 2015
ok, I understand. I will try to play with user defined macros for my needs. Thanks!
24 remaining items
dead-claudia commentedon Oct 13, 2015
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 commentedon Oct 22, 2015
@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.
dead-claudia commentedon Dec 26, 2015
@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 commentedon Dec 26, 2015
My tentative implementation is coming in a PR I'm currently working on, which will fix both this and #23.
iovdin commentedon Mar 25, 2017
i'd expect
(. (foo) (bar))
to be translated tofoo().bar()
which actually translates to
foo()[bar()]
vendethiel commentedon Mar 25, 2017
You want
((. (foo) "bar"))
.iovdin commentedon Mar 25, 2017
@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 commentedon Mar 25, 2017
You might be interested in https://www.npmjs.com/package/eslisp-chain
iovdin commentedon Mar 26, 2017
@vendethiel thanks