Arc Forumnew | comments | leaders | submit | meric's commentslogin
1 point by meric 5421 days ago | link | parent | on: Toy lisp interpreter in lua

    Arc doesn't have Y, I implement Y, and then I write my program.
Its a bit long so I put it here: http://pastebin.org/94450 This is how awesome tables are. :) I see how lisps can implement many features... but I can't see how those features could include adding to the syntax...

    However I wouldn't worry about a couple of error messages :-)
Okay I won't. :)

edit: in the pastebin post on line 14, by 'it' I mean 'OO'

-----

2 points by fallintothis 5421 days ago | link

I see how lisps can implement many features... but I can't see how those features could include adding to the syntax...

Actually, that's an interesting feature of several Lisps: reader macros. For example, in Arc you can make anonymous single-argument functions with some special syntax.

  arc> [+ _ 1]
  #<procedure>
  arc> ([+ _ 1] 1)
  2
In Common Lisp, you are resigned to the more verbose

  [1]> (lambda (x) (+ x 1))
  #<FUNCTION :LAMBDA (X) (+ X 1)>
  [2]> ((lambda (x) (+ x 1)) 1)
  2
What if we want the bracket syntax in Common Lisp? We can extend the parser to dispatch on certain characters, hence "reader macros":

  (set-macro-character #\] (get-macro-character #\)))

  (set-macro-character #\[
                       #'(lambda (stream char)
                           `(lambda (_) ,(read-delimited-list #\] stream t))))
Now when Common Lisp sees an open bracket, it will read from the stream until a closing bracket is found, then shove that all into a (lambda (_) (...)) form.

  [3]> [+ _ 1]
  #<FUNCTION :LAMBDA (_) (+ _ 1)>
  [4]> ([+ _ 1] 1)
  2
Arc doesn't have reader macros (though uses Scheme's for bracket functions; see brackets.scm), but they're an interesting way to work in new syntax (though it probably won't be much fancier than Lisp's already-minimal syntax).

Arc does, however, extend syntax in certain ways. I gave an overview of this so-dubbed ssyntax in another post: http://arclanguage.org/item?id=11179.

This is how awesome tables are.

1. Arc doesn't have destructuring assignment built in, though macros make it easy (see http://arclanguage.org/item?id=8122; it's a bit old, so set is now called assign). Otherwise, the standard approach is

  arc> (= h (table))
  #hash()
  arc> (= h!a 1 h!b 2 h!c 3)
  3
  arc> h
  #hash((b . 2) (c . 3) (a . 1))
2. In Lua, I gather that

  t = { a = 1, b = 2, c = 3 }
is the same as

  t = { ["a"] = 1, ["b"] = 2, ["c"] = 3 }
In Arc, this looks more like strings versus symbols (a datatype I don't think Lua has). Symbols are simply-compared blobs of text, so you often use them as keys in hash tables (e.g., that's what I did in 1). Macros give you a shortcut for this common case.

  arc> (= h (obj a 1 b 2 c 3))
  #hash((b . 2) (c . 3) (a . 1))
3. obj works by quoting the keys you pass in. Quoting literal data will just return that data, so strings can be used in obj, too.

  arc> (= h (obj "a" 1 "b" 2 "c" 3))
  #hash(("c" . 3) ("a" . 1) ("b" . 2))
4. We can't use ssyntax here as in 1 because of its inherent limitations. But we can still do

  arc> (= h (table))
  #hash()
  arc> (= (h "a") 1 (h "b") 2 (h "c") 3)
  3
  arc> h
  #hash(("c" . 3) ("a" . 1) ("b" . 2))
The standard libraries, e.g string, math, etc are tables. e.g math.sin, math.cos.

I wish Arc even had modules. :) But you could do the same thing, in principle.

  arc> (= math (obj sin sin cos cos tan tan))
  #hash((tan . #<procedure:tan>) (cos . #<procedure:cos>) (sin . #<procedure:sin>))
  arc> (math!sin 3.14)
  0.0015926529164868282
In fact, Arc uses this approach for sig.

  arc> (type sig)
  table
  arc> (sig 'rand-key) ; looks like a fn call, but it's a hash lookup
  (h)
  arc> (n-of 5 (rand-key sig))
  (urlend urform admin-gate caris trues)
Tables can have any value as keys besides strings, that includes functions and even other tables.

I think that goes for hash tables in general: anything that's hashable. ;)

  arc> (def f (x) (+ x 1))
  #<procedure: f>
  arc> (= h (table))
  #hash()
  arc> (= (h f) 5)
  5
  arc> h
  #hash((#<procedure: f> . 5))
  arc> (h f)
  5
  arc> (= h2 (obj a 1 b 2 c 3))
  #hash((b . 2) (c . 3) (a . 1))
  arc> (= (h h2) 'hi)
  hi
  arc> h
  #hash((#<procedure: f> . 5) (#hash((b . 2) (c . 3) (a . 1)) . hi))
  arc> (h h2)
  hi
lua doesn't come with it [OO] by default, but you can implement it with tables in a dozen lines

That's how Arc's "object system" works.

  arc> (deftem object a 1 b 2 c 3)
  ((a #<procedure: gs1820>) (b #<procedure: gs1820>) (c #<procedure: gs1820>))
  arc> (inst 'object 'a 5 'c 10)
  #hash((b . 2) (c . 10) (a . 5))
A table can have a `metatable` (mt).

I think I understand what you're saying (it sounds a lot like Python's operator overloading), but I'd probably need to use Lua to appreciate it.

Here's a linked list created with a table.

Aha. I was expecting this earlier: in Lua, you can use tables to make arrays. It's all pretty unified syntactically, whereas Lisps tend to add functionality with Yet Another Macro (instead of overloading syntax).

  arc> (def array values
         (w/table a (on value values (= (a index) value))))
  #<procedure: array>
  arc> (array 'a 'b 'c)
  #hash((0 . a) (1 . b) (2 . c))
  arc> (array 'a (array 'b (array 'c (array))))
  #hash((0 . a) (1 . #hash((0 . b) (1 . #hash((0 . c) (1 . #hash()))))))
If Arc had reader macros, we could make {...} expand into (array ...). But saying "hey, you could bolt that on with a function" is a bit disingenuous: we're still comparing apples and oranges.

-----

2 points by meric 5420 days ago | link

Okay you got me. Lisp can do all that, too ;). About reader macroes: If arc had them and you really liked the {} syntax, would to write a macro for it? And what if you're writing an arc library, would it collide if other people did something else with their braces?

    (it sounds a lot like Python's operator overloading)
re mt: (+ t t1) would fail because they aren't numbers. But if they overloaded the + operator then it would do something instead. Yes it's like python's op-overload but a bit better because of __index and __newindex.

-----

1 point by fallintothis 5420 days ago | link

Lisp can do all that, too ;)

But it's more important whether a language can do something nicely, which I don't necessarily claim Lisp can. After all, Turing machines can do all that, too. :)

would it collide if other people did something else with their braces?

My guess is: yes, they'd probably clash to no end. I'm not familiar with Common Lisp "in the wild", so I don't really know. http://www.faqs.org/faqs/lisp-faq/part2/section-18.html seems to back me up a little.

You could hypothetically package the reader changes into their own modules so that you'd need to explicitly say which braces you're using, modules keep their changes local, etc. Anyone know of work done on this front?

Yes it's like python's op-overload but a bit better because of __index and __newindex.

So are __index and __newindex roughly like in the following (silly) Python code?

  $ python
  Python 2.5.4 (r254:67916, Jan 24 2010, 12:18:00)
  [GCC 4.4.3] on linux2
  Type "help", "copyright", "credits" or "license" for more information.
  >>> class foo:
  ...     def __init__(self, x):
  ...         self.x = x
  ...     def __getitem__(self, index):
  ...         return self.x + index
  ...     def __setitem__(self, index, value):
  ...         self.y = index + value
  ...
  >>> bar = foo(5)
  >>> bar[10]
  15
  >>> bar[20] = 5
  >>> bar.y
  25
If so, I imagine that overloading table operators in Lua becomes all the more powerful since tables are so ubiquitous. Thanks for the explanations.

-----

1 point by aw 5420 days ago | link

You could hypothetically package the reader changes into their own modules so that you'd need to explicitly say which braces you're using, modules keep their changes local, etc. Anyone know of work done on this front?

Hmm, giving the job of custom readers to modules strikes me as making implementing both modules and custom readers complicated and difficult; and makes unnecessary impositions on the programmer: I might well want to implement one part of my module using one syntax and another part of my module using a different syntax.

On the other hand I think being able to choose your custom reader by source code file would simple to use, simple to understand, easy to implement, would make it easy to customize your editor or IDE to understand your different syntaxes by file, and means that modules can also be easily implemented as they could do their work entirely after the source code has been read in.

I wonder if modules could be implemented with namespaces? You want to call my function foo which calls my function bar, but you don't necessarily want to have to name it "foo" in your own code and you want to be able to have your own "bar" without breaking my function foo. I'll have to think about that one.

-----

1 point by fallintothis 5420 days ago | link

I wonder if modules could be implemented with namespaces?

I guess I use the words "module" and "namespace" interchangeably. I'm not sure what else "module" ("package", "namespace", sometimes "library" (loosely), etc.) would mean.

What I meant was that, say, you have two files:

a.arc

  (use "braces-for-table.arc")

  (def table-I-really-want ()
    {foo 1 bar 2 baz 3})
b.arc

  (use "braces-for-set-builder-notation.arc")
  (load "a.arc")

  (def set-I-really-want ()
    { (* v v) for v in (vals (table-I-really-want)) })
You wouldn't want a.arc's use of braces for tables to leak into b.arc, which uses braces for set-builder notation (which you further don't want to clobber the braces in the definition of table-I-really-want).

With a fancier module system, you might even have

b.arc

  (qualify "braces-for-set-builder-notation.arc" s)
  (load "a.arc") ; doesn't qualify its braces for tables

  (def set-I-really-want ()
    s{ (* v v) for v in (vals (table-I-really-want)) })

  (def another-table-I-want ()
    {foo 5 bar 10 baz 15})
Though there shouldn't be a reason you couldn't do something like

c.arc

  (use "braces-for-table.arc")

  (def some-table ()
    {x 5 y 10})

  (use "braces-for-set-builder-notation.arc")
  ; braces hereafter are for set-builder notation

  (def some-set ()
    { x for x in (vals sig) })
As Arc lacks a proper module system, you can already see the clobber-some effects of definitions being global/unqualified. APIs leak all over the place:

  arc> (load "sscontract.arc") ; only really needs to provide sscontract
  nil
  arc> (priority #\!) ; but the user gets all of the implementation details!
  1
Similarly, I don't want to load some library and have implementation-detail reader macros polluting the reader. (If the library's specifically for reader macros, then of course that's okay.)

Even some simple public/private control would be nice. I've played around with this in Arc before. E.g.,

  (let localize nil

    (assign localize
            (fn (expr fs)
              (when (caris expr 'def)
                (if (~mem (cadr expr) fs)
                    `(assign ,(cadr expr) (fn ,@(cddr expr)))
                    expr))))

    (mac provide (fs . body)
      (let private (trues [and (caris _ 'def)
                               (~mem (cadr _) fs)
                               (cadr _)]
                          body)
        `(let ,private nil
           ,@(map [localize _ fs] body))))
    )

  arc> (macex1 '(provide (f g)
                  (def f (x) (h (+ x 1)))
                  (def g (x) (+ (h x) 1))
                  (def h (x) (* x x))))
  (let (h) nil
    (def f (x) (h (+ x 1)))
    (def g (x) (+ (h x) 1))
    (assign h (fn (x) (* x x))))

  arc> (def foo () (prn "outer foo") nil)
  #<procedure: foo>
  arc> (provide (bar)
         (def foo () (prn "inner foo") nil)
         (def bar () (prn "bar") (foo)))
  #<procedure: bar> ; note: no redef warning, since def changed to assign
  arc> (bar)
  bar
  inner foo
  nil
  arc> (foo)
  outer foo
  nil
But this particular approach doesn't interact with macros nicely, doesn't let you qualify namespaces, isn't general, etc. So it's really suboptimal.

-----

1 point by aw 5420 days ago | link

I guess I use the words "module" and "namespace" interchangeably.

By "namespace", I was thinking specifically of MzScheme namespaces, which is where global variables live.

But like you say that doesn't help with macros, so to answer my own question: no, I don't think namespaces are enough to implement modules...

-----

1 point by meric 5420 days ago | link

blushes Yes, like that. Now I feel like when I program in lua I do so in an ivory tower.

    Lua 5.1.4  Copyright (C) 1994-2008 Lua.org, PUC-Rio
    > t_mt =  { __index = function(self, k)
    >>         return self.x + k
    >>     end,
    >>     __newindex = function(self, k, v)
    >>         self.y = k + v
    >>     end }
    > 
    > function foo(x)
    >>     return setmetatable({x = x, y = 0}, t_mt)
    >> end
    > 
    > bar = foo(5)
    > print(bar[10]) --> 15
    15
    > 
    > bar[20] = 5
    > print(bar.y) --> 25
    25

-----

1 point by fallintothis 5420 days ago | link

  return setmetatable({x = x, y = 0}, t_mt)
I find this line really interesting. If I'm reading it right, you can alter the metatable (operator overloading) on the fly? So you could pass in a table as a parameter, set a metatable, play with some operators, then set a different metatable to overload operators differently?

In Python, the operators are kind of tied to specific classes. I guess you can go about casting and converting and inheriting and monkeypatching and such, but I don't think it's as straightforward as just setting a new metatable. Admittedly, I can't think of when I've ever needed to alter operator-overloading dynamically like that, so I've never really tried. Do you know if it's useful in Lua code? Or am I completely off-base here?

-----

1 point by meric 5420 days ago | link

Hmm, the only times I've used it as where I'd be casting objects in other languages... I've used it to 'cast' between 'tables' doing similar things. like `my1.rect` to `my2.rect`, if they both use .x, .y, .width and .height to store their coordinates. (Not sure that's a good practice because they can change.) Yes, you can alter metatables any time, but I don't do it that often.

-----

1 point by aw 5421 days ago | link

but I can't see how those features could include adding to the syntax...

I'm not sure if this is what you mean, but if you want to create your own syntax, one option is to use http://awwx.ws/extend-readtable0. What this does is allow you to specify a particular character that you want to trigger a call to your own parser. When the reader sees that character, it calls your parser, which reads your syntax from the input and returns the Arc value that you want your syntax to generate.

For example, here's a simple syntax parser for literal tables (from http://awwx.ws/table-rw3):

  (def parse-table-items (port (o acc (table)))
    (scheme.skip-whitespace port)
    (if (is (peekc port) #\})
         (do (readc port) acc)
         (with (k (read port)
                v (read port))
           (= (acc k) v)
           (parse-table-items port acc))))

  (extend-readtable #\{ parse-table-items)
The example above returns a table value, but you can also return a list, which can then be expanded as a macro:

  arc> (extend-readtable #\@ (fn (port) `(at ,(read port))))
  #<void>
  arc> (read "@3")
  (at 3)
  arc> (mac at (v) `(+ ,v 1))
  #(tagged mac #<procedure: at>)
  arc> @3
  4

-----

1 point by meric 5421 days ago | link | parent | on: Toy lisp interpreter in lua

"This interpreter tries to follow the awesome "Scheme from Scratch" articles written by Peter Michaux." I learnt alot from his code too! I thought about doing some thing like "implement R4RS" eventually but figured I should learn a bit more about lisp first. I think its possible to do scm/arc together.. I'm not sure you even need to change the eval function...

-----

1 point by meric 5421 days ago | link | parent | on: Toy lisp interpreter in lua

That doesn't change the fact that I shouldn't use t for anything other than `t`, but good point.

-----

1 point by meric 5424 days ago | link | parent | on: Does Arc have (2-dim) arrays?

Currently hash table -> (tab key key2) returns tab.key . It'd be cool if (tab key1 key2) returns ((tab key1) key2)

-----

1 point by rocketnia 5424 days ago | link

Actually, (hash key default) behaves more like (or hash.key default), except that default is evaluated either way (since hash isn't a macro).

-----