Arc Forumnew | comments | leaders | submitlogin
3 points by Pauan 4750 days ago | link | parent

I just added in keyword destructuring:

  > (let (:a :b :c) (obj a 1 b 2 c 3)
      (list a b c))
  (1 2 3)


2 points by akkartik 4749 days ago | link

Here it is in wart:

  mac let(vars f . body) :case (and cons?.vars (keyword? car.vars))
    `(with ,(collect:each k vars
              (yield sym.k)
              (yield:f sym.k))
       ,@body)
It took me several hours to get to this; in the process I uncovered lots of things I'd forgotten to implement :) And bugs :/

http://github.com/akkartik/wart/compare/37d4a2d736...d38d477...

When I try to eval:

  let (:a :b) (table 'a 1 'b 2) (cons a b)
..it cranks for a good second and a half, and conses over a million cells. (It also leaks 24 cells; that's top priority to fix.)

-----

3 points by akkartik 4747 days ago | link

I've modified ssyntax precedence to follow rocketnia's suggestion[1] rather than arc. Now[2] it looks like this:

  mac let(vars f . body) :case (and cons?.vars keyword?:car.vars)
    `(with ,(collect:each k vars
              (yield sym.k)
              (yield f:sym.k))
       ,@body)
[1] http://arclanguage.org/item?id=13974

[2] git clone git://github.com/akkartik/wart.git && git checkout 2007e158c8

-----

1 point by Pauan 4747 days ago | link

"(with hash ...)"

This is very similar to a macro in Arubic-style namespaces:

  (eval-w/ foo ...)
The above evaluates the expression "..." in the "foo" namespace, which may potentially be a table. That's not quite right, though. Here's the correct version:

  (eval-w/ (new-namespace foo namespace)
    ...)
The above could then be easily wrapped in a macro like "w/table", or even overloading the "with" macro as you did.

---

"..it cranks for a good second and a half, and conses over a million cells."

I'm not entirely sure how such a simple expression could cause so much consing. What is wart doing with all those cells?

-----

2 points by akkartik 4747 days ago | link

"What is wart doing with all those cells?"

It's turtles all the way down, but I need to better understand the details.

-----

1 point by akkartik 4750 days ago | link

Interesting. Why not just destructure to (a b c) and call it table destructuring?

-----

3 points by Pauan 4749 days ago | link

Because that would require hash table's keys being sorted, and then the results would vary depending on what keys the hash table has. For instance:

  (let (a b c) (obj d 4 c 3 b 2 a 1)
    (list a b c))
Should that be (3 2 1) or (1 2 3)? Or maybe (4 3 2)? But, that's assuming hash table keys are sorted in the first place, when they aren't.

In addition, that would require a runtime check to determine whether the argument is a hash table or a list. With my method, the check is done at compile-time: no runtime costs.

Also, it works on more than just hash tables. In fact, it works on anything that accepts 1-2 arguments, where the first is a symbol. Thus, if you created a new data type, you could use keyword destructuring on it. Basically, this:

  (let (:a :b :c) (obj a 1 b 2 c 3)
    (list a b c))
Compiles into the equivalent of this:

  (withs (g1 (obj a 1 b 2 c 3)
          a  (g1 'a)
          b  (g1 'b)
          c  (g1 'c))
    (list a b c))
Compare that to the following:

  (let (a b c) (list 1 2 3)
    (list a b c))
Which compiles into the equivalent of this:

  (withs (g1 (list 1 2 3)
          a  (car g1)
          g1 (cdr g1)
          b  (car g1)
          g1 (cdr g1)
          c  (car g1))
    (list a b c))
Interestingly, that means you could create a data type that worked with both kinds of destructuring. For instance, an alist might support the `foo!bar` syntax, and then you would need a way to distinguish between "look this up based on the key" and "look this up based on the index".

As a final note, from a conceptual standpoint, I view keyword arguments as being implemented with an implicit hash table. Thus using keyword syntax to destructure a hash table makes lots of sense to me.

In other words, if these two are equivalent:

  (let (a b c) foo ...) 
  (apply (fn (a b c) ...) foo)
...then I view these two as equivalent as well:

  (let (:a :b :c) foo ...)
  (keyword-apply (fn (:a :b :c) ...) foo)

-----

2 points by akkartik 4749 days ago | link

"I view keyword arguments as being implemented with an implicit hash table. Thus using keyword syntax to destructure a hash table makes lots of sense to me."

Interesting. If you make keyword args pervasive and optional like I do, you need a sense of ordering as well in the table, which weakens that imagery.

"Should that be (3 2 1) or (1 2 3)?"

It should be whatever you define it to be :) Making the symbols keywords didn't actually help me avoid that question at first glance.

"With my method, the check is done at compile-time."

But how does it perform the check for this expression?

  (let (:a :b :c) h
    ..)
Are you using the presence of keywords to disambiguate whether or not to insert the g1 (cdr g1) pairs in the withs?

-----

1 point by Pauan 4749 days ago | link

"Interesting. If you make keyword args pervasive and optional like I do, you need a sense of ordering as well in the table, which weakens that imagery."

Oh I'd love to have pervasive keywords like Python (or wart), but it seems Racket doesn't support that. And I don't plan for Nu to become an interpreter. Keywords are optional in Nu, though (or will be, once I've implemented them).

---

"Making the symbols keywords didn't actually help me avoid that question at first glance."

I just like how there's a clean (and visible) separation between "lookup by key" and "lookup by index". It also avoids a runtime check as well, which is nice.

---

"Are you using the presence of keywords to disambiguate whether or not to insert the g1 (cdr g1) pairs in the withs?"

Yes. The code the compiler outputs depends on whether the argument is a keyword or not.

-----

1 point by akkartik 4749 days ago | link

I was playing around with the idea and thought of an alternative syntax:

  (with hash
     ..)
I even came up with a kinda icky implementation in wart:

  mac with(vars . body) :case (~cons? vars)
    `(with ,(collect:each (k v) eval.vars
              yield.k
              yield.v)
       ,@body)
Pros: it's shorter to use because I don't need to repeat the keys.

Cons: it only works with syms. You can't eval an expr that returns a hash. It replaces the runtime check you're concerned about with that super ugly eval-inside-unquote. You can't import just a few of the keys so it feels a little like 'using namespace std' in C++ -- you may not know what vars you're going to get, and you may end up overriding bindings.

Perhaps I should just make it a new term. That would address the first limitation:

  (w/bindings hash
    ..)
Meh, I'm not sure how I feel about this.

-----

1 point by rocketnia 4749 days ago | link

You're not going to be able to use (with hash ...) with a local variable 'hash unless you have at least one of these:

- Fexprs. An fexpr implementation of 'with can use the complete value of 'hash as it determines how to treat the unparsed body.

- Static typing with record types, so that a macro can use the type of 'hash as it determines how to treat the unparsed body.

- Some variant of JavaScript-style scope chain semantics, in the sense that a bare variable reference means (or can mean) a field lookup in general. IMO, this would be the most straightforward to add to an Arc-3.1-like compiler, since it's a matter of compiling foo to (scope 'foo), (with foo ...) to (let ((scope (shadow (scope 'foo) scope))) ...), and other scope-related things in their own analogous ways.

- Something else I haven't thought of. :)

-----

1 point by rocketnia 4749 days ago | link

"Oh I'd love to have pervasive keywords like Python (or wart), but it seems Racket doesn't support that."

Racket has pervasive keyword arguments though, right? What's in your way?

-----

2 points by Pauan 4749 days ago | link

I assumed akkartik was talking about this:

  (def foo (a b)
    (list a b))

  (foo 1 2)       -> (1 2)
  (foo :b 2 :a 1) -> (1 2)
That doesn't work in Racket. You need to explicitly say that the arguments are keywords:

  (def foo (:a :b)
    (list a b))
In other words, arguments are either positional or keyword-based, but not both at the same time. This is different from Python, which lets you treat an argument as either one:

  def foo(a, b):
      return [a, b]

  foo(1, 2)     -> [1, 2]
  foo(b=2, a=1) -> [1, 2]

-----