arclanguage / anarki

Community-managed fork of the Arc dialect of Lisp; for commit privileges submit a pull request.
http://arclanguage.github.io
Other
1.17k stars 160 forks source link

Nested Tables in arc #46

Closed Kinnardian closed 8 years ago

Kinnardian commented 8 years ago

What's the best (or even a good) way to build nested tables in arc? For example in Javascript this is pretty straightforward:

var pg = {
    name:"Paul Graham",
    age:55,
    hometown: "Weymouth, Dorset"
};
var jfk = {
    name:"John Kennedy",
    age:93,
    hometown: "Boston, Massuchussets"
};
var people = {pg, jfk};
console.log(people.pg.hometown); // Weymouth, Dorset

I'm having quite a bit of trouble doing something analogous in arc.

akkartik commented 8 years ago

That Javascript syntax actually is creating an object containing tables, so the keys are implicit in the declaration of people. In Arc you'd do:

(= x (obj name "Paul" age 55 hometown "Weymouth"))
(= y (obj name "John" age 93 hometown "Boston"))
(= people (obj x x y y))
(prn people!x!hometown)
Kinnardian commented 8 years ago

Just to check my understanding objects and tables are the same thing in arc, right? That's what I gathered from the documentation here: http://arclanguage.github.io/ref/table.html

akkartik commented 8 years ago

Yes, they're the same. We prefer to say table everywhere rather than object. We should probably remove the one occurrence of the word 'object' on that page..

Kinnardian commented 8 years ago

I find the (obj ) macro most-confusing. Why isn't that called (table )

akkartik commented 8 years ago

table is a function for initializing an empty table. obj is a macro for initializing a table with contents. I agree, it's a little redundant. In Wart I had a single name. We can't overload the name in Arc because of Racket's constraints, but perhaps we should rename obj to table and table to something else.

Kinnardian commented 8 years ago

The new table could call the old table in the event of no args and there would then be one macro for initializing an empty table or one with contents

akkartik commented 8 years ago

Yup. Already obj works for both purposes.

Kinnardian commented 8 years ago

(obj ) wont take a variable as a key parameter will it?

(= nickname "pg")
(= pg (obj name "Paul" Hometown "Weland")
(= jfk (obj name "John" hometown "Boston"))
(= people (obj nickname pg jfk jfk))
(pr people!pg!name) ;; error
(pr people!nickname!name) ;; "Paul"
akkartik commented 8 years ago

No, obj quotes the keys. If I want a variable key I just do assignments in a loop or something.

(each person people-list
  (= (people person.id) person))
Kinnardian commented 8 years ago

That's unfortunate. I wonder what it would take to make it so that variable keys, assuming they were typecast/coerced to strings, worked. Sort of like the javascript bracket accessor:

var car={};
var property = "make";
car[property]="Toyotoa";
console.log(car.make); // "Toyota"

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Property_Accessors

akkartik commented 8 years ago

It's not hard. All you have to do is remove one quote from the definition of obj in arc.arc:

(mac kinnard-obj args
  `(listtab (list ,@(map (fn ((k v))
                           `(list ,k ,v))
                         (pair args)))))
Kinnardian commented 8 years ago

Would that break anything?

akkartik commented 8 years ago

If you define obj that way? Of course. But it's safe if you give it a new name and use it in your code.

shader commented 8 years ago

If you don't want the argument quoted, you don't need a macro. Either call 'listtab directly or write a simpler function wrapping it. Maybe something like:

(def kinnard-obj items
  (listtab:pair items))
akkartik commented 8 years ago

D'uh!

Kinnardian commented 8 years ago

(listtab ) doesn't appear to work on nested lists:

arc> (list "pg" '(("hometown" "weyland") ("age" 56)))
("pg" (("hometown" "weyland") ("age" 56)))
arc>  (listtab (list "pg" '(("hometown" "weyland") ("age" 56))))
car: contract violation
  expected: pair?
  given: "pg"
  context...:
   /Users/Kinnard/Desktop/anarki/ac.scm:638:0: ar-xcar
    map1
    listtab
   /Users/Kinnard/Desktop/anarki/ac.scm:1251:4

Alternatively:

arc> (listtab (pair "pg" '(("hometown" "weyland") ("age" 56))))
Can't take cdr of "pg"
  context...:
   zz
   /Users/Kinnard/Desktop/anarki/ac.scm:1251:4

But that's coming from (pair )

akkartik commented 8 years ago

Yes, it can't know what you want to do with inner lists. Do you expect (list 3 (obj a 4 b 5)) into a list of lists too? Functions operate on the top level by default.

Kinnardian commented 8 years ago

In that case I'd expect a list (3 #hash((a . 4) (b . 5))) if lists are allowed to contain objects/tables and (3 ((a 4) (b 5))) if not.

So there's no straightforward way to create a nested table from a list or a string?

akkartik commented 8 years ago

If you want to write a function that creates a nested table you can. It's just not very often useful, in my experience. Is there anything in particular you're trying to do? It's not like there's anything analogous to this in any other language either, is there?

Anyways, this is a good exercise :) Write a function that takes a nested list and turns all lists into tables :) (What should you do if any list has an odd number of elements?)

akkartik commented 8 years ago

In that case I'd expect a list (3 #hash((a . 4) (b . 5))) if lists are allowed to contain objects and (3 ((a 4) (b 5))) if not.

Yes, lists are allowed to contain objects. My point was this: if you don't expect list to turn nested tables into lists, why would you expect table to turn nested lists into tables?

akkartik commented 8 years ago

You're using listtab wrong in your previous example. It requires an association list, which I showed you before. It's a list of pairs.

arc> (listtab '((a 1) (b 2)))
#hash((a . 1) (b . 2))

I notice the online help doesn't have an example for listtab. I'll add it now.

akkartik commented 8 years ago

Sorry, I've been responding to your questions one at a time with no memory of the thread as a whole. Are you basically trying to create the original list of lists in a single line or something like that? Was my response in #1 not what you wanted for some other reason?

Kinnardian commented 8 years ago

Nested tables just seem to be the norm in javascript from my experience. I've heard "Everything is an object" in js.

This does seem like a good exercise, though I loathe the timing. Here listtab takes a list: http://arclanguage.github.io/ref/table.html

I wonder if there's ambiguity between als and lists throughout those docs.

akkartik commented 8 years ago

I think the idea of the original author was that "list of key/value pairs" would be clearer than "association list".

BTW, here's where that terminology comes from, in Common Lisp: https://www.cs.cmu.edu/Groups/AI/html/cltl/clm/node153.html

Nested tables just seem to be the norm in javascript from my experience. I've heard "Everything is an object" in js.

That is true. I think you meant to express your original example in Javascript as:

var people = {
 bob: {
    name: "Bob Sampson"
    age: 55
    hometown: "Bangor, Maine"
  },
  charlie: {
    ...
  }
}

Yes, Javascript has the edge here, because Lisp has no literal syntax for anything but lists (and strings). But now show me how you'd construct a nested object of objects in Javascript without curly brackets. What standard functions are there to construct objects out of lists?

The syntactic restrictions and the uniformity of using only parentheses for all syntax have their costs. This lack of syntax for literal objects is one of them.

akkartik commented 8 years ago

And yes, the al argument is intended (in the absence of type information) to convey that the argument is expected to be an association list.

akkartik commented 8 years ago

Ack, I think I misused "D'uh" before, @shader. I meant it in the sense of "I can't believe I missed something so obvious!" :)

shader commented 8 years ago

I don't think you really want to have a function that converts any nested list structure to a nested table structure; what if you wanted some of the values to be lists?

The right answer is to do it like in JavaScript, and pass tables as values. They just have syntactic sugar for objects. The closest in arc would be something like

(obj 
  a (obj 1 2)
  b (obj 3 4))
Kinnardian commented 8 years ago

I had wanted originally to do something along the lines of:

(= age 450 hometown "Cambridge" username "nwtn" name "Isaac Newton") 
(= (people username) (obj username username name name age age hometown hometown ))
  (save-table people peoplefile*)

That appears to be working.

I had at first attempted (obj (obj ) ) approach but that gave me the problem of passing a in variable key. https://github.com/arclanguage/anarki/issues/46#issuecomment-203178795 That lead me to employ (listtab ) per @shader recommendation but that turned out to be a rabbit hole of sorts since listtab works on association lists, not on lists or on nested lists for that matter. https://github.com/arclanguage/anarki/issues/46#issuecomment-203186605 This does work for variable key assignment:

(def var-key-table items
  (listtab:pair items))

But the whole problem was obviated by the (= (people username) . . . ) above. What's this sort of lookup called? It probably should have been clear to me that I could take this approach from @akkartik's recommendation: https://github.com/arclanguage/anarki/issues/46#issuecomment-203181732 But it wasn't.

Broadly it seems I need to get a better understanding of lists and tables in arc and it seems the functionally could be augmented with more methods or perhaps a utility library.

@akkartik et alia, do you think it would be possible to implement a new "table literal" special syntax using curly braces? {name "Rick James" occupation "Singer"} ==(obj name "Rick James" occupation "Singer")

Kinnardian commented 8 years ago

Really what I wanted (and still want) was an associative array . . . with arbitrarily deep nesting

akkartik commented 8 years ago

@akkartik et alia, do you think it would be possible to implement a new "table literal" special syntax using curly braces? {name "Rick James" occupation "Singer"} ==(obj name "Rick James" occupation "Singer")

I don't know. Lisps have something called reader macros and you might be able to build this in Racket. But I don't have much experience with them. Look at brackets.scm sometime when you understand Arc better.

But it doesn't seem worth the trouble. Is obj really so much more trouble to write out? Would curly braces help much, given you'd still not be able to pass in variable keys?

I'd say spend some time learning what the language has and its idioms of combination before you try to change it. Don't compare it with other languages, accept it for what it is for a time. Not that I was ever very good at following this advice when learning a new language :)

Broadly it seems I need to get a better understanding of lists and tables in arc

Yes. Hey, have you gone over the official tutorial? http://www.arclanguage.org/tut.txt. It also applies to anarki except for one gotcha with for, and it goes over lists and tables pretty well.

and it seems the functionally could be augmented with more methods or perhaps a utility library.

No, it's a failure of documentation. The methods are close enough to ideal that it wouldn't make much difference to your learning. Or it might help you but hinder someone coming from a different language. Or it might provide too wide a menu of choices which would confuse everyone even more.. Just suck it up :) This fitful process is what it takes to learn something new and different.

Go over the tutorial. Paul Graham is a better writer than any of us here.

akkartik commented 8 years ago

Really what I wanted (and still want) was an associative array . . .

But table does provide associative arrays, right? Is there any functionality it doesn't have? You're just missing the syntax you're used to, and you came to Lisp to leave behind the crutches of syntax and learn the value of macros in return. Just suck it up, and type in (obj where you'd type { and ) where you'd type '}'.

Kinnardian commented 8 years ago

The terminology seems convoluted everywhere:

http://cs.stackexchange.com/questions/6678/relation-and-difference-between-associative-array-and-hashing-table http://stackoverflow.com/questions/2884068/what-is-the-difference-between-a-map-and-a-dictionary http://programmers.stackexchange.com/questions/30908/difference-between-hash-and-dictionary https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map https://en.wikipedia.org/wiki/Associative_array

So it might be better to describe behavior: An arbitrarily nestable key-value structure that allows "pushing" and has a homoiconic instantiation and where a uniques constraint can be imposed on keys the way an "array" can only have one 0 index and one 1 index. I'd only be interested in the uniqueness constraint on the first level in this case and I think regular arc tables would work for anything deeper. I think arc's templates come into play somewhere here but I was having trouble with them and still learning tables anyway.

Sort of like

person= {
    name:string
    age:number,
    hometown: string
};
pg= new person("Paul Graham", 56, "Some town in England");
var people={};
people.push(pg);

I am still getting used to arc and lisp in fact. Still breaking the "thinking in other languages" habit.

shader commented 8 years ago

Maybe it won't help, but here's my attempt at disambiguating the nuances between the terms:

As for your desires and examples, I'm not sure I understand the problem. There are many differences between arc and other languages, but the hash tables are not one of them. They're really quite standard.

You may be running into an issue with the fact that lisp has symbols as a separate literal type, while most languages only have strings and sometimes support syntactic sugar to interpret barewords as strings. JS does that for example. Honestly, I prefer the support for symbols, and would much prefer a JS that had them as well.

Another thing I don't quite understand is your desire for the ability to 'push' - JS objects do not actually have a push method, nor do any other map / dictionary / hash table types that I know of. JS arrays do, but they are really wrapper objects around hash tables that implicitly map sequential numbers to anonymously injected items. So a = []; a.push('foo'); a.push('bar') produces an object {0: foo, 1: bar}. I'm sure you could create such a wrapper around an arc obj if you wished; it is likely not what you actually want however.

Normally insertion into a hash table is done as stated above, through explicitly setting a key to match a value. Some implementations are stuck with methods like tab.insert(k, v), but many languages support direct assignment like tab[k] = v - this is how we usually do it in arc as well. You can do it explicitly with (sref tab k v), but usually we take advantage of some magic to use (= (tab k) v) or more succinctly (= tab.k v) and (= tab!key v).

There are two layers to those methods. The first is that we can overload application of non-functions to do other things, such as looking up a key in a table. That's why (tab k) actually looks up k in tab, instead of complaining loudly about trying to apply something that's not a function.

The second is that '= is extendable to detect the form of whatever place it's assigning to, and use the appropriate setter. This makes for very symmetric code, as you can use the same expression for assignment and lookup, almost like pattern matching.

In the end, I'm not really sure what you're having difficulty with, but I hope something up there helped a little. I'd also recommend trying to stay away from mutating data structures as much as possible, but that's somewhat unrelated.

Templates are somewhat like object prototypes, but they don't provide much besides type tagging and default values. You still have to explicitly identify each property when you create them, so I sometimes define a constructor to go with them for brevity.

(deftem person
  name nil
  age nil
  hometown nil)
(= people* (table))
(mac new-person (id name age hometown)
  `(= people*.id (inst 'person 'name ,name 'age ,age 'hometown ,hometown)))
(new-person pg "Paul Graham" 56 "Some town in England")

It would be easy to add more helpers around building these things, but it's hardly worth it.

Kinnardian commented 8 years ago

Thanks @shader that all does provide some additional clarification. It wouldn't have occurred to me to employ a macro; I'm still getting used to having them as an option and understanding when they're the right choice.

shader commented 8 years ago

This case doesn't really need a macro; all it does is prevent evaluation of the ID, making it a symbol. There are much better examples for when to use macros elsewhere.

Generally macros are the right choice when you want different syntax, assignment to one of the names you pass in, or more control over the evaluation of another form. pg's book On Lisp has lots of really good examples, and the chapter on macros in Practical Common Lisp is also pretty good.

One of the common patterns is a "with" macro, that provides context to some code, like w/infile:

(mac w/infile (var path body)
  `(let ,var (infile ,path)
      ,@body
      (close ,var)))

This both provides a variable during the execution of 'body, and does extra work before and after.

That was a bit of a tangent though.

On Thu, Mar 31, 2016, 23:25 Kinnardian notifications@github.com wrote:

Thanks @shader https://github.com/shader that all does provide some additional clarification. It wouldn't have occurred to me to employ a macro; I'm still getting used to having them as an option and understanding when they're the right choice.

— You are receiving this because you were mentioned.

Reply to this email directly or view it on GitHub https://github.com/arclanguage/anarki/issues/46#issuecomment-204229429