Arc Forumnew | comments | leaders | submit | Adlai's commentslogin

I'm not sure about your related note, but for an example of symbol macros in use:

This example is from Common Lisp's object system, called CLOS. When you want to do some code while accessing specific slots of an object, you can use a 'with-slots form, which lets you name symbols that you'll use to reference specific slots of the object. The 'with-slots form then generates local symbol-macros for each symbol you mentioned, which expand into code to access those slots. An example, taken from PCL [1]:

  (with-slots ((bal balance)) account  ; Here 'balance is the name of the slot, and 'account is the specific object
    (when (< bal *minimum-balance*)    ; *symbol* is a CL convention for global variables
      (decf bal (* bal .01)))))        ; decf is like --
[1] Practical Common Lisp, by Peter Seibel, available online at www.gigamonkeys.com/book/

-----

2 points by Adlai 5802 days ago | link | parent | on: Multi character matching, and ssyntaxes

Another idea along this line is to enable code like this:

  arc> (let str "Hello world!"
         (= (str "world") "arc")
         str)
  "Hello arc!"

-----

1 point by bogomipz 5801 days ago | link

Nice idea! This would eliminate both pos and posmatch.

Call the string with an index to get the character at that position, call it with a character or string to find the index.

Although, given your example, it looks like (str "world") should return a range.

-----

1 point by shader 5801 days ago | link

That doesn't sound too hard to do. Assignment and position on strings are handled by string-set! and string-ref. If those were modified to accept a string as input instead of just a numerical index, then Adlai's code would work.

Maybe we should just make two scheme functions str-set! and str-ref and use those instead, as opposed to over-writing the original functions.

This sounds like a good spot for the redef macro ;)

Anyway, because position matching and assignment are handled separately, (= (str "world") "foo") could still work even without (str "world") returning a range.

-----

1 point by bogomipz 5801 days ago | link

Yes, there just seems to be a dilemma of whether (str "world") should return an index or a range. If Arc had multiple return values, it could return the start and end indices, and a client that only uses the start index would just ignore the second value :)

-----

2 points by Adlai 5800 days ago | link

The return value should correspond to what was being searched for.

In other words, searching for one character should return an index, while searching for a substring should return a range.

There are thus four operations which would ideally be possible through ("abc" x):

  arc> (= str "hello arc!")
  "hello arc!"
  arc> (str "arc")
  6..8     ; or some other way of representing a range
  arc> (str #\!)
  9
  arc> (str 5)
  #\space
  arc> (str 4..7)   ; same as previous comment
  "o ar"
A way to take advantage of multiple values, if they were available, could be something like this:

  arc> (str #\l)
  2
  3

-----

1 point by conanite 5800 days ago | link

Just curious - wouldn't it suffice to return the index of the beginning of the matched string when running a substring search?

  arc> (str "arc")
  6
, because you already know the length of "arc", so you don't really need a range result?

Otherwise, these are great ways to occupy the "semantic space" of string in fn position.

-----

1 point by shader 5800 days ago | link

I agree with you. I don't think that returning a range is necessary.

Even if call position and assignment weren't handled separately, it would still be possible to work off of the length of the argument and the index, without needing a range.

The question is whether or not pg agrees with us enough to add it to arc3 ;)

-----

1 point by conanite 5800 days ago | link

If there are 100 arc coders in the world, there are probably 100 versions of arc also. The question is whether you want it in your arc :)

-----

1 point by shader 5800 days ago | link

True. And I do. Unfortunately, I'm busy working on several other things at once right now. If you want to start working on it, be my guest. Hopefully I'll be able to share what I've been doing soon.

-----

1 point by Adlai 5800 days ago | link

I guess that (str "world") could just return an index, because (= (str "world") "arc") has access to the entire call, and can thus calculate

  (+ (str "world") (len "world"))
to figure out what the tail of the string should be after a substitution.

-----

1 point by shader 5800 days ago | link

Well, scheme supports multiple values, so it shouldn't be too hard to get them into arc, right?

-----

1 point by conanite 5800 days ago | link

arc supports multiple values via destructuring

  (let (a b c) (fn-returning-list-of-3-things)
    ...)
In the particular case of returning multiple indexes into a string though, you don't usually know in advance how many matches there will be, so destructuring isn't an option.

-----

1 point by Adlai 5800 days ago | link

Multiple return values from a form are allocated on the stack, not on the heap. I don't 100% understand what that means, though...

One practical consequence is that you don't have to deal with later multiple values if you don't want to, but when values are returned as a list, you have to deal with them.

-----

1 point by Adlai 5803 days ago | link | parent | on: How about + for tables?

What you describe seems to be a mapping -- you're mapping the polymorphic '+ on each pair of values in the table.

Extending the idea of the polymorphic '+ on lists to include tables suggests what CatDancer proposed in the original post.

So I wrote up the following sketchy hacks of '+ for tables:

  ; arc3/tab-tests.arc

  (def table+ tbs
    (listtab (apply +
                    (map tablist
                         (rev tbs)))))
  ;; Conceptually transparent, but try to avoid
  ;; thinking about the wastefulness...

  (def table+-better tbs
    (w/table tb-new
      (each tb (rev tbs)
        (ontable k v tb
          (= (tb-new k) v)))))
  ;; Much better! Has a nice profile too :)

  ;; The rest is a "throw-away" test for the code
  ;; (require 'human-at-monitor-to-compare-tests)
  (with (ta (obj a 'ta
                 b 'ta)
         tb (obj b 'tb
                 c 'tb)
         tc (obj c 'tc
                 a 'tc))
    (map prn `("Consecutive lines should be identical:"
               ,(table+ ta tb tc)
               ,(table+-better ta tb tc)
               ,(table+ tb tc ta)
               ,(table+-better tb tc ta)
               ,(table+ tc ta tb)
               ,(table+-better tc ta tb))))

  ; Arc REPL
  Use (quit) to quit, (tl) to return here after an interrupt.
  arc> (load "tab-tests.arc")
  Consecutive lines should be identical:
  #hash((a . ta) (b . ta) (c . tb))
  #hash((a . ta) (b . ta) (c . tb))
  #hash((a . tc) (b . tb) (c . tb))
  #hash((a . tc) (b . tb) (c . tb))
  #hash((a . tc) (b . ta) (c . tc))
  #hash((a . tc) (b . ta) (c . tc))
  nil
So, the functions work. However, to get this as the behavior of '+ for tables would require adding a clause in ac.scm, to the following definition (from line 710):

  (xdef + (lambda args
             (cond ((null? args) 0)
                   ((all string? args) 
                    (apply string-append args))
                   ((all arc-list? args) 
                    (ac-niltree (apply append (map ar-nil-terminate args))))
                   (#t (apply + args)))))
My definition uses a bunch of higher-level functions defined quite a ways into arc.arc, but I need to use them in a low-level function in ac.scm. Should I implement the functions by hand into ac.scm, or is there a better solution?

EDIT: Someday I'll manage to format my code correctly the first time around...

-----

1 point by CatDancer 5803 days ago | link

is there a better solution?

try redefining + in Arc, for example

  (redef + args
    (apply (if (all [isa _ 'table] args)
                table+-better
                orig)
           args))
where redef is

  (mac redef (name args . body)
    `(let orig ,name
       (= ,name (fn ,args ,@body))))

-----

1 point by Adlai 5803 days ago | link

I guess putting the definition of + on hash tables into arc.arc makes sense. Either one of the algorithms is a more high-level procedure than the other behaviors of '+.

I like this 'redef macro. How come it's not in arc.arc already? I can't think of any reason to not have this be there, along with the definitions of other key macros like 'do, 'def, and 'mac.

Now, granted, this function actually contributes nothing to <insert large project here, including news.yc>. However, I think that this function strikes at the heart of the "hackable language" mentality, by making it much easier to quickly modify behavior on the fly. It also sends a message to somebody reading the source code: "One of the key principles in this language is that you can redefine behavior across the board with one macro call."

I think that for quick exploratory patches, it's a better solution than editing the original definition. If I want to modify this behavior, I just have one self-explanatory (redef + ...) form to edit, rather than having to sift through the original '+ definition. It is a clean solution, which adds hackability to the language.

-----

2 points by shader 5803 days ago | link

It's already included in Anarki, which until arc3 came out was the version used by the majority of the arc community. Probably still is.

-----

1 point by CatDancer 5803 days ago | link

I like this 'redef macro

You might also like my extend macro: http://catdancer.github.com/extend.html

You know, I had completely forgotten that I had used + on tables in my example of how to use extend...!

-----

1 point by Adlai 5803 days ago | link

'extend is great! However, I think it's more of a great library/patch, than something that should sit with the core functions. 'redef, on the other hand, is elegant, transparent ('extend is transparentially challenged...), and works well for quick hacks. 'extend seems to me to be more suited for setting in stone some behavior sketched out with 'redef (for example, the method-like behavior that you described in the original thread about 'extend http://arclanguage.org/item?id=8895)

The example you have of + on tables uses the algorithm that conses up a storm...

-----

1 point by CatDancer 5802 days ago | link

One thing I like about 'extend is that I can run it several times while developing an extension without my older, buggy versions remaining on the call chain. Try actually hacking with this version of 'redef, it turns out to be really quite a pain!

  arc> (redef + args
         (apply (if (sll [isa _ 'table] args)
                     table+-better
                     orig)
           args))
oops, typo, try again

  arc> (redef + args
         (apply (if (all [isa _ 'table] args)
                     table+-better
                     orig)
           args))
oops, this 'redef is now calling the previous 'redef, I'm stuck!

On the other hand I don't think the explicit separation in 'extend between the test of whether to apply the extension from the implementation of the extension has turned out to be all that useful, so maybe some combination of the two would be better.

-----

1 point by Adlai 5802 days ago | link

Rudimentary hack to fix that:

  (= old-def (table))

  (mac redef (name args . body)
    `(let orig ,name
       (= (old-def ',name) orig)
       (= ,name (fn ,args ,@body))))

  (mac undef (name)
    `(= ,name (old-def ',name)))
I tried storing a list as each value in the table, and popping or pushing to/from the list in redef and undef, so that you could step backwards more than just one definition at a time. However, that made the entire thing much more unstable, because it turns out that 'push and 'pop rely on a bunch of functionality which breaks quite easily if you're messing with the internals.

So, this stripped down version is my little 'redef/'undef hack-pack.

-----

1 point by Adlai 5803 days ago | link | parent | on: "ac-tunnel" proposal

I think that FOR NOW, 'mz is fine. Even though each person has their own patched-up Arc, it seems as though the vast majority are based on pg's Arc on mzscheme. If somebody is using an Arc built on some other VM (eg Rainbow on the JVM), then they will be putting quite different code within the body of an 'mz, so I don't think it's an issue that they'd also have to change the name of the symbol.

-----

1 point by Adlai 5804 days ago | link | parent | on: How to make this function simpler?

Probably a stupid question, but:

Does that mean that I should just manually patch ac.scm to comply with the new functions?

-----

1 point by CatDancer 5804 days ago | link

If you need the change right away you can patch ac.scm yourself, or, if you don't mind waiting, pg will eventually have a new arc3.tar containing the update.

-----

2 points by Adlai 5805 days ago | link | parent | on: How to make this function simpler?

Very useful... I'm surprised that it's not the "standard" yet. Nice job!

-----

1 point by Adlai 5805 days ago | link | parent | on: How to make this function simpler?

It looks good! Thank you also for using my idea.

Side note: compare the "profiles" of this version, and the earlier versions -- this one has a much more "functional" profile.

I'm a bit confused what you mean about atend:err. Do you basically mean that there would be function composition between a macro, and a call to, for example, (err "missing char after backslash")? Something like

  (mac atend (alert)
    `(unless (peekc (stdin))
       ,alert))
I think I'm missing something...

EDIT: I get it now. I hadn't noticed that you only use /atend[-:]err/ at points where the next character might "correct" the error.

-----

1 point by CatDancer 5805 days ago | link

Do you basically mean that there would be function composition between a macro, and a call

Yes, a composition, though not a function composition. Because the Arc compiler rewrites (a:b ...) as (a (b ...)) when a:b appears in the first position in an expression, it works for macros also.

Thus

  (atend:err "missing close quote")
expands into

  (atend (err "missing close quote"))
which macro expands into

  (unless (peekc (stdin))
    (err "missing close quote"))

-----

1 point by Adlai 5805 days ago | link | parent | on: Question about alist functions in arc.arc

It be very neat for alists to be usable like tables and strings, as "functions". That would almost be like a duck-typed "database" interface. Obviously some functionalities are specific to each (shadowing older pairs in an alist, having five bazillion keys in a hash table), but it would make sketching out an application much easier -- you could change only one or two spots in your code to switch between a table or alist, and the rest of your code would still work fine. Maybe this could be another use for 'annotate -- to differentiate between "a list" and "an alist"?

In the meantime, it should be easy to write a "polymorphic" lookup function:

  (def lookup (db key)       ; obviously just a sketchy
    (if (atom db) (db key)   ; definition -- but Arc is LFSP
        (alref db key)))     ; so there's no warranty :)

-----

1 point by Adlai 5805 days ago | link | parent | on: How to make this function simpler?

Alrought, here's what I've cooked up. I'm not sure whether this is any better than CatDancer's original -- I was doing it more as a highly applied introduction to Arc :)

I copied out the code, commented it (spending about 80% of the time between www.arcfn.com and arc.arc), and then made two main changes. One is that I rewrote 'json-unicode-digits to only pass over the 4 chars once, rather than three times -- I did this by dissecting 'firstn, and putting the error checking forms inside there.

The other major change is the macro. I noticed that three functions used a similar pattern (the last one was a slight stretch, but after all, a 'let is a lambda deep inside).

My code:

  (def hexdigit (c)
    (or (<= #\a c #\f) (<= #\A c #\F) digit.c))

  (def json-backslash-char (c)
    (case c
      #\" #\"
      #\\ #\\
      #\/ #\/
      #\b #\backspace
      #\f #\page
      #\n #\newline
      #\r #\return
      #\t #\tab
      (err "invalid JSON backslash char" c) ) )

  ;; (The above two are verbatim from CatDancer's code
  ;;  Now comes some modified stuff)

  (def json-unicode-digits (j)
    (let u ((afn (n xs)
              (if (is n 0) nil
                    (or (no xs) (no:hexdigit (car xs)))
                  (err "need 4 hexadecimal digits after \u")
                    (cons (car xs) (self (-- n) (cdr xs)))))
            4 j)
      (coerce (int (coerce u 'string) 16) 'char) ) )

  (mac def-jsniffer (name c next (o arg 'cdr.j))
    `(def ,name (j)
       (if (is car.j ,c)
           (,next ,arg))))

  ;; ... because it sniffs ahead at the next char.
  
  (def-jsniffer match-json-unicode-escape #\u
    [list (nthcdr 4 _)
          (json-unicode-digits _)])

  (def-jsniffer match-json-backslash #\\
    [do (if no._ (err "missing char after backslash"))
        (or match-json-unicode-escape._
            (list cdr._ (json-backslash-char car._)) ) ] )

  (def-jsniffer match-json-string #\"
    (fn ((j a))
      (list j (coerce rev.a 'string)))
    ((afn (j a)
       (if no.j (err "missing close quote"))
       (if (is car.j #\") (list cdr.j a)
           (iflet (j c)
                 (match-json-backslash j)
               (self j (cons c a))
             (self (cdr j) (cons (car j) a)) ) ) )
     cdr.j nil))
Well, I hope that my experiment might have some useful ideas for you!

Adlai

-----

1 point by CatDancer 5805 days ago | link

I think this is really the key part:

  (mac def-jsniffer (name c next (o arg 'cdr.j))
    `(def ,name (j)
So we have three answers to the question, what to do if you're passing some state through a lot of functions, to the extent that passing it around is taking up as much code as what you're actually doing?

1. Put all the functions inside a surrounding lexical scope, and keep the state in some lexical variables that all the functions can access.

2. Write some macros so that you're still passing the state through all your functions, but the variables don't appear in your code.

3. Use some kind of dynamic binding (like Scheme parameters) so the functions can access the state without it having to be passed in as part of the function's arguments.

-----

1 point by Adlai 5806 days ago | link | parent | on: Bind a list of variables

I do feel stupid. However, this creates new bindings. From the code that shader posted, I think he doesn't want to create a new lexical binding. Is there some way of using destructuring for assignment?

-----

More