Arc Forumnew | comments | leaders | submit | almkglor's commentslogin
2 points by almkglor 6412 days ago | link | parent | on: arc2c update

Okay, now that I've actually gotten to see it - I think you forgot to push codegen.arc, since the current version on git don't actually generate code that calls DBL2OBJ.

Edit: Also, it might be useful to organize the primitives as "functions", like what I did with cons_fun().

So:

  #define CONS() {obj d = POP(); TOS() = cons_fun(TOS(), d);}
This should allow us to express some primitive calls directly:

  (%cons foo (%car bar))
  ==>
  PUSH(cons_fun(foo, car_fun(bar)));
Also, it might be useful to merge 'aquote and 'alit structures into a single structure.

-----

1 point by sacado 6411 days ago | link

Oops, thank you :/ I just pushed codegen.

I'll work on what you said about primitive functions in a few days (Monday or Tuesday I guess).

-----

2 points by almkglor 6407 days ago | link

Err, no. I just realized while on vacation that it's not safe to use memory-allocating primitives as functions. The problem is with nested conses:

  QUOTE_CONSTANT[0] = cons_fun(FIX2OBJ(1),cons_fun(FIX2OBJ(2),NILOBJ))
Let's call the cons_fun(FIX2OBJ(1) ...) call cons_fun1, and the other as cons_fun2. cons_fun2 is called first (because it's more inner). The value is then pushed into the C stack. Then cons_fun1 is called. However, what if GC is triggered at exactly cons_fun1? Then the cons cell returned by cons_fun2 is on the C stack, not the Arc stack; the C stack is not scanned by the GC! This means we actually lose memory areas.

Probably means I have to modify the way constants are stored.

-----

1 point by sacado 6406 days ago | link

Hmmm, good point. The stack, GLOBALS and QUOTE_CONSTANTS are all explored when GC is triggered. Maybe the solution would be to add a temporary storage (that should be taken into consideration by GC too) before putting things in the constants, or something like that ?

-----

1 point by almkglor 6406 days ago | link

> Maybe the solution would be to add a temporary storage (that should be taken into consideration by GC too) before putting things in the constants, or something like that ?

Called the "Arc stack", maybe? ^^

Currently this is what is done:

  QUOTE_CONSTANTS[0] = cons_fun(FIX2OBJ(1),cons_fun(FIX2OBJ(2),NILOBJ))
It should probably be:

  PUSH(FIX2OBJ(1));
  PUSH(FIX2OBJ(2));
  PUSH(NILOBJ);
  CONS();
  CONS();
  QUOTE_CONSTANTS[0] = POP();
Of course, the thing about having _fun functions is, they are safe for non-allocating functions. So we could safely directly translate:

  (car (cdr foo))
to:

  PUSH(car_fun(cdr_fun(LOCAL(3)/*foo@42*/)));
Edit: of course, even though it's named "_fun" doesn't mean that it is a function:

  #define car_fun(o) (((pair *)(o))->car)

-----

2 points by almkglor 6406 days ago | link

Okay, converted init_constants() to use the Arc stack instead of the C stack. Fear the pointer arithmetic foo-ness when using a processor with special PUSH and POP instructions (which are arguably dying out, because RISC processors handle stacks using explicit pointer ariths).

-----

2 points by almkglor 6412 days ago | link | parent | on: Lessons from Goo (a Lisp dialect)

== Easy variables

Hmm. We could redefine 'do in such a manner that we could introduce variables directly in the sequence, like so:

  (do
     (niaw)
     (var x 42)
     (woof x)
     (var z 99)
     (grrr x z))

  ==>

  (do
    (niaw)
    (with (x 42)
      (woof x)
      (with (z 99)
        (grr x z))))
== short names

This looks like a good lesson to learn.

== op wins

Arc's [ ... _ ... ] syntax approximates a limited form of SRFI-26 'cute or Goo's op; having a separate syntax also makes it stand out.

-----

2 points by absz 6412 days ago | link

Careful! From http://www.paulgraham.com/arclessons.html:

Implicit local variables conflict with macros.

In previous Lisps if you wanted to create a local variable you had to do it explicitly with a let. In Arc we were planning to let users declare local variables implicitly, just by assigning values to them. This turns out not to work, and the problem comes from an unforseen quarter: macros.

If you're going to declare local variables implicitly, you have to decide what the scope of the new variable will be. We decided we would make new variables local to blocks: a local variable created implicitly by assignment would have a scope that extended to the nearest enclosing do (Arc's progn).

...

What convinced us to toss it was not a technical problem but a social one. In a language with implicit local variables and macros, you're always tripping over unexpected lexical contours. You don't want to create new lexical contours without announcing it. But a lot of macros that don't look like blocks in the call expand into blocks. So we provided a second block operator, called justdo, which was like do but didn't create a new lexical contour (i.e. it is Common Lisp progn), and this is what you were supposed to use in macroexpansions.

The trouble was, I kept forgetting and using do instead. And I was thereby writing utilities with the worst sort of bug: the kind that might not show up for years, and only then in someone else's code.

...

This problem is not limited to Lisp. Macros and implicit local variables just don't seem to work well together. Meaning that any language that already has implicit local variables will run into trouble if they try to add macros.

----------

Maybe your "explicit var" would work, but it might just put the problem back from the other direction, so to speak. Perhaps if you give macros some form of "lexical hygiene" (yeah, yeah, hygiene bad, etc.)... How does Goo do it?

-----

1 point by eds 6412 days ago | link

http://arclanguage.org/item?id=1736

-----


'range is an arc.arc function in canonical Arc, so you should be leery of redefining it - if you decide to use some random library, and it uses 'range in the canonical sense, you can expect it to break.

Instead, define it in subfunction form:

  (def directawayfrombound (creature)
    (withs (range
              cons
            within
              (fn (range num)
                (let (min . max) range
                  (<= min num max)))
            awaydir
              (fn (pos dim dir)
                (if (within (range 0 (- dim 1)) (+ pos dir))
                    dir
                    (- dir)))
            (xpos ypos) creature!pos
            (xdir ydir) creature!dir)
      (list
        (awaydir xpos (len world.0) xdir)
        (awaydir ypos (len world.0) ydir))))

-----

1 point by Darmani 6412 days ago | link

Thanks for the correction. Although I did know about the canonical range function, it slipped my mind when I wrote that (though I did have the feeling that there was a "real" range function). I should have mentioned that I wasn't near any computer with Arc when I wrote, though I still should have known better.

-----

1 point by bOR_ 6412 days ago | link

Both of you, thanks!, more learning arc. (1) "withs" is "with sequentially"', so that "range" makes sense in the function "awaydir". (2) cons isn't just appending a number to a list.

I like the table!key syntax, not sure what I'm thinking about world.0 as a syntax, especially because world.0.0 doesn't give what I'd expect (first element of the first element of world), but just returns the same as world.0

  (def makeworld (x)
     (= world (n-of x (n-of x nil))) ; world of nil
     nil)

-----

1 point by Darmani 6412 days ago | link

Actually, almkglor's code would (I'm pretty sure) have worked with just a normal with; function definitions are unevaluated at definition, so that (fn (x) (afergergghersgergferg x)) would not give "Error: afergergghersgergferg is not a function" until it's called. If a defined a function called afergergghersgergferg in the meantime, as with a normal with statement, it should have worked.

Anyway, you're not alone in being surprised that x.y.z expands to (x y z) instead of (x (y z)).

-----

2 points by absz 6412 days ago | link

Not quite. It works in the toplevel because doing (def name args ...) is effectively to (= name (fn args ...)); this establishes a global, not a lexical, binding. Using let/with/withs adds a lexical binding, and since functions (or "closures") close over their scopes, they can't get at functions defined at the same time. Thus, you need to use withs. Observe:

  arc> (with (foo (fn (x) (* 2 x)) bar (fn (y) (foo y)))
         (prn (foo 10))
         (prn (bar 10))
         t)
  20
  Error: "reference to undefined identifier: __foo"
  arc> (withs (foo (fn (x) (* 2 x)) bar (fn (y) (foo y)))
         (prn (foo 10))
         (prn (bar 10))
         t)
  20
  20
  t

-----

1 point by bOR_ 6411 days ago | link

That implies that at the top level, the order in which I define my functions (which use each other) doesn't matter..

nice! Then I can group them in a way that is more sensible for humans.

Anyway, the program is now far enough that I've a critter running around the field and filling its memory with associations between the things it encounters. Next stop is to let it learn what is edible and to let it steer itself based on its observations :).

-----


First tip: Avoid using '( ... ) . Use (list ...) instead. When you learn about mutating objects, then use '( ... ) , and only for optimization or if you really, really want to.

  (def directawayfrombounds (creature)
    (with ((oldxdir oldydir) creature!dir
           (oldxpos oldypos) creature!pos
           delimit
           (fn (minpos oldpos maxpos olddir)
             (if
               (is oldpos minpos)
                 (if (is olddir -1)
                     1
                     olddir)
               (is oldpos maxpos)
                 (if (is olddir 1)
                     -1
                     olddir)
               ; else
                 olddir)))
       `(,(delimit 0 oldxpos (- (len (world 0)) 1)
                   oldxdir)
         ,(delimit 0 oldypos (- (len (world 0)) 1)
                   oldydir))))

-----

1 point by bOR_ 6413 days ago | link

So.. (let newdir '(nil nil) should become (let newdir (list nil nil) and creature!pos is equivalent to (creature 'pos) and you make a temporary anonymous function within a function but still give it a name (delimit, through the with command) that does the comparing. Three new things learned :).

I'll see if I can improve it some more though, might be able to do something with testing whether position + car / cadr direction would put a creature out of bounds in the world array, and then multiply the direction by -1. Not at home now, so will have to wait.

-----

2 points by almkglor 6415 days ago | link | parent | on: arc2c update

Okay, looks good.

As an aside: maybe it's better to make the primitives really, really primitive?

Basically we define separate %n+ for adding two fixnums, %f+ for adding two flonums, %fn+ for adding a flonum and a fixnum (in that order) and, %nf+ for adding a fixnum and a flonum (in that order).

Then we can write + in lib-ac.scm.arc as:

  (set +
    (fn rest
      ($+ rest)))
  (set $+
    (fn (args)
      (if args
          (if (%cdr args)
              ($sub+ (%car args) (%cdr args))
              (%car args))
          0)))
  (set $sub+
    (fn (accum rest)
      (if rest
          ((fn (val rest)
             (if (%is (%type accum) 'int)
                 (if (%is (%type val) 'int)
                     ($sub+ (%n+ accum val) rest)
                     (if (%is (%type val) 'num)
                         ($sub+ (%nf+ accum val) rest)
                         ; raise exception
                         ()))
                 (if (%is (%type accum) 'num)
                     (if (%is (%type val) 'int)
                         ($sub+ (%fn+ accum val) rest)
                         (if (%is (%type val) 'num)
                             ($sub+ (%f+ accum val) rest)
                             ; raise exception
                             ()))
                     ; raise exception
                     ())))
            (%car rest) (%cdr rest))
          accum)))
As for macros: Hmm. You see... Look, up in the sky! Is it a bird? A plane? Superman? Nothing? Oh, what were we talking about again? ^^ LOL

Okay now serious: currently I'm blocked with creating a "protected" eval for macros. Programmer's block. The bit blocking me is the destructuring thing. Erk.

-----

1 point by sacado 6415 days ago | link

I think you're right for primitives. I asked pg the same question a few time ago, he answered he didn't know yet. Anyway, for an optimizing compiler, we will need these (as in : "hmmm, n can only hold a fixnum, let's translate this heavy '+ in a lightweight '%n+ !")

Now, macros. I tried to think about it yesterday, and I don't really understand why you need this protected mode. I mean, macros have to be defined (globally) before they are called and treated as such. Since you can already identify all the globals (and since a macro is a global thing annotated by 'mac), it should be easy to

- make a list of all the symbols holding a macro at a given moment in the code

- explore all the sexprs to see if one of those symbols is the first element of any sublist

- if it is to, make the transformation

- do it again until nothing changes anymore

Am I missing something obvious ?

-----

3 points by almkglor 6414 days ago | link

  (do
    (= xe 0)
    (mac somethingstupid ()
      (= xe (+ xe 1))
      `(prn "something stupid" ,xe)))

... and 'xe is a function in....?

-----

2 points by sacado 6414 days ago | link

OK , so I was missing something obvious :)

If I understand well, we have to interpret the code as we compile it (as a macro's body is executed when a function is defined/compiled, not when executed) ? To me, macros were something translating a piece of code into another one, period. But that's not always the case as your example shows. If someone writes

  (mac foo
    (prn "blah"))

  (def bar (n)
    (foo) (* n 2))
That means we have to display "blah" on compile time, right ? Hmm, that's much harder than I thought... It really means implementing an arc interpreter in arc (the way the official one is written in mzscheme), with for example prepending all the interpreted names with _ to distinguish them from the compiler's names...

Ok, I think I'm starting to realy get it ; or am I still missing an even trickier issue ?

-----

2 points by almkglor 6413 days ago | link

That is about it. ^^ However my current implementation uses tables as environments for the function. Keys in the table are symbols for the variable names in that environment. A "parent" string key accesses the environment's parent.

The global environment is shadowed by a table; when this table is accessed, if a table entry does not exist, we look it up in the real environment using 'eval (!!) and retain a copy. This allows the macro to use stuff like 'map.

However there is still a leak here: if the real global-level environment has a function that modifies a global, it will modify a different global variable from the one that is visible to the macro. This shouldn't happen anyway: we should really avoid using globals in utility functions ^^.

-----

3 points by almkglor 6415 days ago | link | parent | on: Poll: ssyntax

Looks like you have to add something like:

  /  (msg L 'r)
capital L means left-associative, so foo/bar/nitz is grouped {{foo / bar} / nitz}.

As an aside, why not <- ? Basically parent<-owner<-id means "send 'id to the result of sending 'owner to parent".

For that matter if an object, such as parent above, is something of very specific type, you can even use the Anarki 'defcall syntax. For example if messageable objects are of type 'messageable, you can do:

  (defcall messageable (o m)
    (msg o m))
which creates the relation:

  (foo bar) == (msg foo bar), if (is (type foo) 'messageable)
Then (msg parent 'owner) is simply parent!owner. If you load the new Anarki ssyntaxes.arc, then parent!owner!id is ((parent 'owner) 'id)

-----

1 point by dreish 6413 days ago | link

Thanks for the tips.

I considered <- (along with a lot of other things), but rejected it because the left-associativity of it seemed counter-intuitive, it didn't stand out as much as slashes, and I want it to stand out because it's inconsistent with symbol syntax, it's confusing with -> which conventionally means type-conversion (and which doesn't break up symbols), and it's three times as much effort to type as /.

Slashes are fairly common in symbols, but not at the beginning (except for / itself), so it's unambiguously parseable, though maybe it won't work with this system, now that I think about it. I don't see how to prevent embedded slashes from meaning anything special without the first slash. I.e., I want foo/bar/nitz to be one symbol, but /foo/bar/nitz to be a symbol and two msg calls. Maybe that's too hairy, but I'd hate to break things like w/uniq.

I guess the general solution to any syntax that is confusing to people is a good syntax-highlighting editor.

-----

3 points by almkglor 6413 days ago | link

Well, for that matter having (foo msg) mean "send msg to foo" would be much shorter, and also shows that Scheme was originally a synchronous message-passing language anyway.

-----

1 point by dreish 6413 days ago | link

Yeesh, why didn't I think of that?

That seems clearly to be the best way to do it. Thanks again.

-----

2 points by almkglor 6416 days ago | link | parent | on: How would you code this in Arc?

> so where is the state info kept regarding the index of the last match, etc.?

In the stream state?

-----

4 points by almkglor 6416 days ago | link | parent | on: Poll: ssyntax

Given that it didn't originally have good Unicode support ^^

-----

2 points by almkglor 6416 days ago | link | parent | on: Poll: ssyntax

Which is why I'm polling, instead of arbitrarily adding them. ^^

-----

2 points by almkglor 6417 days ago | link | parent | on: Poll: ssyntax

> (do you often use andf & orf ?)

Yes, in arc2c ^^

-----

3 points by sacado 6417 days ago | link

Oh, well, ok then... :)

But anyway, I still think they're not worth loosing an ASCII character, and using mathematical notation would be very useful. It would make code readable by people a little aware of mathematics. That is, most programers. It would be definitely better than arbitrary characters.

Why should we restrict to ASCII anyway ? I mean, a lot of symbols I use are not ASCII anymore (they are accentuated, I'm French so something like 'year is translated into 'année, not into 'annee). Sure, they're hard to type, but are they any longer than they symbol counterpart ? If you type them often, just make them a vi macro (or whatever in your favorite text editor) and you're done.

It might end up looking like APL, for sure, but I think Fortress, Sun's new language designed by Guy Steele, is going that way too. And Steele cannot be wrong :)

-----

5 points by absz 6417 days ago | link

I don't mind non-ASCII, I mind weird non-ASCII. Even in English, we leave ASCII behind: “As I was going to the café—what fun—my naïve friend said…” It's just that I don't know of any keyboard layout that supports ∧ or ∨. I agree that they would look great, as would Fortress.

I wonder if anyone's given any though to using (La)TeX in a language? So that something like (number{\wedge}acons var) would be the same as (number∨acons var)? Or just as a nice way of typing things like Fortress? (Which I agree looks very interesting.)

-----

2 points by almkglor 6416 days ago | link

> Oh, well, ok then... :)

I'd probably use them a lot more often if the syntax was a little easier, which is why I suggested using ssyntax for them. Currently the syntax is ((orf this that the-other) foo), and doubled starting parens feel rather strange to me.

-----

More