Arc Forumnew | comments | leaders | submit | Adlai's commentslogin
1 point by Adlai 5944 days ago | link | parent | on: Bind a list of variables

Alright, here is my first ever Arc code:

  (mac read-into (vars form)
    `(do
       ,@(map (fn (var) `(= ,var ,form)) vars)))
Note that this generates code which evaluates 'form once per variable, which I think is what you want.

For some reason, I couldn't for the life of me get the [_] syntax to work. Anyways, to do what you were trying, you'd just call, with my macro defined:

  (read-into (a b c) (readb in)))
By the way, I think that "readb in" can't be right. Maybe you want (readb (stdin))?

Adlai

EDIT:

Got it to work with the [] fn abbreviation, and it's the most hideous butchering of a beautiful thing that I've ever seen:

  (mac read-into (vars form)
    `(do
       ,@(map [quasiquote (= ,_ ,form)] vars)))
The problem is that

  [`(= ,_ ,form)]             ;expands into

  (fn (_) (`(= ,_ ,form)))    ;instead of

  (fn (_)  `(= ,_ ,form))     ;ugghh!!!

-----

1 point by absz 5944 days ago | link

The behaviour of the [] syntax makes perfect sense, if you think about it. First, [...] is just an abbreviation for (fn (_) ...) to save typing.[1] Second, `(...) is just an abbreviation for (quasiquote '(...)). Thus [`(...)] is [(quasiquote ...)], which is (fn (_) ((quasiquote ...)). If [] stripped off the outer layer of parentheses, then [+ _ 1] would become (fn (_) + _ 1), which is clearly wrong. Thus, we see that it makes more sense to use fn, since the whole point of [] and ` is to save typing and make code clearer, neither of which they can do here.

Another, possibly nicer, way to think about it is that [] just represents a delayed function call; you're trying to delay an object, and since [] is a function call, you're calling it instead. Either way, the point is that this is the consistent and desirable way for [] to work.

[1]: On Anarki, you also get _1, _2, etc., and __ for varargs, but that's not important for this discussion.

-----

1 point by shader 5943 days ago | link

Yep. Unfortunately, in this case it's practically shorter, and more readable, to use the fn form:

  (fn (_) `(= ,_ ,form))
because you don't have to type out the whole name "quasiquote".

-----

1 point by Adlai 5943 days ago | link

Just a little curious idea:

What about a special syntax (for this one situation), where you can put the backquote thus: [` = ,_ ,form] which would read-macro-expand to (fn (_) `(= ,_ ,form))

I guess it's just a question of how common this problem is, and how difficult it would be to implement this type of read-macro. I guess brackets.scm goes on my list of files to check out...

-----

1 point by shader 5943 days ago | link

in is just a input-port that I would already have bound, or passed in. If you'll notice, that was just a standalone expression, with no context, to illustrate what I was trying to do.

Thanks for the code, that was exactly what I was thinking of. Too bad there isn't a macro or function that just returns the value of a variable in such a way that it can be assigned to. Can anyone think of how to implement such a thing?

i.e.:

  (= v 'a)
  (= (val v) 6) ;the variable a is now assigned the value 6.
Basically an (unquote) without the wrapping quote ;)

-----

1 point by Adlai 5943 days ago | link

Well, there's always 'eval, but it has issues and stigmas, and I've read that other Lispers look at you funny when you use it your code...

'in is also a built-in macro in arc.arc. Since Arc is a Lisp-1 (grr...), you can't bind it to an input stream without potentially breaking some other code...

  (mac in (x . choices)
    (w/uniq g
      `(let ,g ,x
         (or ,@(map1 (fn (c) `(is ,g ,c)) choices)))))
Back to the symbol dereferencing question -- I think one reason that this kind of thing is shied away from is that it starts to smell an awful lot like pointers. However, the control over evaluating that you get with macros is more than enough to take care of symbol dereferencing problems like this.

-----

1 point by shader 5943 days ago | link

Right. I forgot to check the symbol before I used it like I sometimes do, via 'help or 'src (Anarki).

Is there anything wrong with "pointers"? As in this case, they can often be quite useful, and make the code (at least to me) simpler. Maybe I just think in pointers, and need to learn to use destructuring binding more. That won't work unless you know all of the variable names in advance, but I guess that's what a hash table is for :)

-----

2 points by almkglor 5942 days ago | link

Here's a simple Arc-F package (which is intended to work on hl in the future) that provides pointers.

Pointers are dereferenced by (ptr), so you use (ptr) to get the contents and (= (ptr) v) to assign.

  (in-package pointer)
  (using <arc>v3) ; replace with <hl>v1
  (interface v1
    pointer)
  ; replace with (mac (pointer e) ...)
  (mac pointer (e)
    (if (acons e)
        (let vars (map [uniq] e)
          `(with ,(mappend (fn (var val) `(,var ,val)) vars e)
             ; replace with (tag...)
             (annotate 'pointer
               (cons (fn () ,vars)
                     (fn (v) (= ,vars v))))))
        (w/uniq v
          `(annotate 'pointer
             (cons (fn () ,e)
                   (fn (,v) (= ,e ,v)))))))
  ; replace with (defm (sref (t p pointer)... ) ...)
  (defm sref ((t p pointer) v)
    ((cdr:rep p) v))
  (defcall pointer (p)
    ((car:rep p)))
Usage:

  (= stuff (list (pointer a)
                 (pointer (car b))
                 (pointer c!x)))
  (each p stuff
    (pr (p))
    (= (p) (something)))
If someone's ported nex3's 'defcall and 'defm onto Anarki-on-arc3, the above can be made to work by just removing the package stuff.

-----


It's a good start -- it works, which always is a plus :)

However, a few comments about your version:

- You use an iterative 'while, rather than the tail-recursive approach that Paul Graham posted. Both end up eventually as tail-recursive code, however, I think that it's better, in this case, to write the tail-recursion directly. The reason for this is that in your version, you have to explicitly "break out" of the loop (by setting 'tries to 1), while in Paul's version, he just doesn't call the next "iteration".

- You should probably use 'prn for the last message -- when I run this, "nil" gets tacked on to the end of the message.

- This is more just a matter of convention, than an actual problem, but your indentation isn't completely standard. Usually, body code for macros is only indented by two spaces. Also, having several tokens on one line can get a bit confusing, and in a form like 'with, I think it helps to organize them. I think the start of your code could be more readable like this:

  (def guess ()
    (with (tries 10
           r (rand 100))
      (while (> tries 1)
        etc)))
As for some derisive best practice about optimization -- "Premature optimization is the root of all evil." (Donald Knuth)

-----

2 points by shader 5938 days ago | link

Does anyone know which forms are indented two spaces and which are indented to the beginning of the second item on the previous line?

For example, def, mac, with, while, etc. seem to be indented two spaces, but if, do, and most function calls are indented more.

-----

3 points by CatDancer 5938 days ago | link

If all the following lines go together as a group, and the arguments on the first line aren't part of that group, then I'll indent the following lines differently to emphasize the grouping:

  (each (k v) mytable
    (prn "my key is " k)
    (prn "and my value is " v))
It doesn't matter to me whether it's a macro or a function:

  (copy (obj a 1)
    'b 2
    'c 3)
I use the two space indentation a lot more with macros than I do with functions because many macros use the pattern of some arguments followed by a body and not many functions do, but I don't change my indentation depending on whether it's a function or a macro.

If the lines after the first line don't fall into their own group, then I'll line up the arguments:

  (+ '(a b c d)
     '(e f g h)
     '(i jk))

  (map (fn (x) ...)
       (generate-my-list ...))
For me the important consideration is using indentation to show which arguments go together.

-----

1 point by Adlai 5938 days ago | link

I think there's a general tradition in Lisp indentation, which works essentially as follows:

Function calls are indented with all the arguments starting at the same column, for example:

  (calculate-fn-of arg1
                   arg2
                   arg3)
Macro calls are indented with the arguments two spaces in, for example:

  (while (< x 10)
    (prn x)
    (++ x))
This is usually done to emphasize the "body" of the macro arguments. So in macros which take several arguments before the "body" code, you'd put those arguments either on the same line, or indented in some way to distinguish them from the body. For example:

  (with (var1   val1
         thing2 val2
         var3   expr3)
    (do
      (stuff)
      (with-vars)))
However, some forms are indented differently for clarity. This doesn't seem to be as standardized. An example would be Arc's 'if macro. It makes sense to indent it with all the conditions on one line, but where do the result forms go? Possibilities are:

  (if (test1)
       (response1)
      (test2)
       (response2)
      (response3))

  (if (test1) (response1)
      (test2) (response2)
              (response3))

  (if (test1)
      (response1)
      (test2)
      (response2)
      (response3))

  (if
    (test1)
      (response1)
    (test2)
      (response2)
    (response3))
I actually haven't seen the last form used at all, but it seems very clear to me. I'm guessing some convention will emerge over time.

-----

1 point by shader 5938 days ago | link

I just noticed that there is a file named CONVENTIONS in Anarki that states comment and indentation conventions for arc. They say pretty much what has been said so far, but may be worth looking at if you are interested.

-----

1 point by conanite 5938 days ago | link

I usually

  (if a b
      c d
        e)
if it fits, otherwise

  (if a
      b
      c
      d
      e)
The wavy-style if indentations make me seasick.

-----

1 point by Adlai 5938 days ago | link

I think that your first example is probably best in terms of compactness and clarity (associating each condition with its corresponding code). The completely straight indentation can be difficult to scan quickly if you have more than one condition. IMO, the only problem with your first example is when the conditions span more than one line or are very long.

-----

1 point by Adlai 5938 days ago | link

In terms of 'if, I was looking through some of pg's code just now, and noticed that he does use the wavy form of indentation. Thus, if you're big on proof by authority, the correct form of indentation for 'if would be the wavy one...

-----

3 points by conanite 5938 days ago | link

I was looking through some of pg's code just now

arc code or scheme code?

I once heard a great teacher say that appeal to authority is flawed rhetoric, so I never do it :)

-----

2 points by shader 5937 days ago | link

Never heard that one before ^^

However, in this case I agree with Adlai: pg had even planned on getting ppr to "properly indent long ifs". I presume that means to implement the wavy feature. It's also the convention for Anarki, according to the CONVENTIONS file.

So, here's what I'm thinking should be done for ifs:

1) If you only have three expressions, if-then-else, use:

  (if a
      b
      c)
1) If you like the format, and the expressions are very short use:

  (if a b
      c d
      ...
        e)
3) If you don't like that form, or the expressions are longer, use:

  (if (a       )
        (b       )
      (c       )
        (d       )
      (e       ))
I'm not sure whether to use one or two spaces between clauses. The anarki conventions file uses two spaces. Elsewhere I've seen only one space. So, which is it? Now or never ;)

(I'm writing a new version of ppr with proper indentation of forms, in case you wanted to know why I'm so interested in the indentation question)

-----

2 points by Adlai 5937 days ago | link

I think two is better, because it a) clearly distinguishes it as indentation (rather than just an accidental #\space), and b) it's consistent with the indentation of macros & co.

-----

1 point by shader 5935 days ago | link

Two spaces it is.

Another question:

How do people indent do, and, or, etc.:

a)

  (do abc
      bcd
      cde)
or b)

  (do
    abc
    bcd
    cde)
Choice a is more like a function call, b is more like a macro. Should it be a community standard, or on a user by user basis?

-----

1 point by CatDancer 5935 days ago | link

I use A, for me the space taken up by the extra indentation is less than the extra line taken up for B.

I don't use indentation as a way to indicate whether I'm calling a function or using a macro.

-----

1 point by shader 5935 days ago | link

Next question:

Should case be indented like 'if:

  (case
    a
      (b     )
    c
      (d     )
    (e      ))
or in pairs like with:

  (case
     a (b    )
     c (d    )
     (e     ))
?

Should it depend on whether they fit on one line or not? If they don't fit, should it still be like b., just with the second part spilling onto the next line?

-----

1 point by CatDancer 5935 days ago | link

I use the second if it fits OK, or the first if they don't fit.

-----

1 point by absz 5934 days ago | link

Ditto, except I write

  (case
    a-short     (b ...)
    c-very-long (d ...)
                (e ...))
when everything is on one line, and

  (case
    a-short
      (b ...)
    c-very-long
      (d ...)
    ; else
      (e ...))
when it doesn't (though I go back and forth about the ; else).

-----

1 point by shader 5933 days ago | link

Your first version is the one currently implemented by ppr.arc. If you would like, you can write the indentation function for your second version (it can even include the "; else")

Otherwise I think that the first version is generally clearer, and the second is rarely needed, as the value of the case statement can't usually be very long. Could you give me an example where you use your second version?

-----

1 point by absz 5933 days ago | link

Sure; here's something from my tagged-unions.arc on Anarki (Arc 2). It's responsible for parsing the types specified for slots in the tagged union:

  (each type-frag types
    ; Assemble the ('type name) pairs into two lists, so that we can iterate
    ; through them.
    (case (type type-frag)
      ; If it's a symbol, then we're defining a new name; add a new set of
      ; values, and go.
      sym
        (do
          (zap [cons type-frag _] names)
          (zap [cons nil       _] values))
      ; Otherwise, we're adding a value to an existing variant.
      cons
        ; I changed my mind about the order (now it's (name pred?) instead of
        ; (pred? name)), so I'm reversing it here.
        (zap [cons (rev type-frag) _] (car values))
      ; else
        (err "Invalid member of tagged union declaration.")))
I should also add (just as another data point) that my multi-condition ifs look like that too, and single-condition ifs look like the following:

  (def macexn (n expr)
    " Macroexpand `expr' `n' times.  NB: `expr' *is* evaluated!
      See also [[macex1]] [[macex]] "
    (if (and (number n) (> n 0))
      (macexn (- n 1) (macex1 expr))
      expr))
I'm not hugely wedded to any of my conventions, though, and I haven't coded in Arc for quite a while anyway; this isn't intended to get you to change anything or convert people to my style. As I said, it's just another data point.

-----

1 point by Adlai 5938 days ago | link

Well, the 'if macro only exists (as a macro) in Arc.

However, the indentation actually isn't completely consistent. There are a bunch of places where pg does a straight indentation for 'if.

-----

1 point by pg 5938 days ago | link

I've never felt 100% sure about it though.

-----

1 point by Adlai 5938 days ago | link

Well, I think it adds just enough clarity to be worth the "seasickness". I guess it's also related to the indentation from CL 'cond:

  (cond ((condition1)
         (result-code1))
        ((condition2)
         (result-code2))
        (default-code))

-----

1 point by coconutrandom 5939 days ago | link

:D thank you! I didn't know both end up with tail recursion and the 'nil was also bugging me

I've already installed Arc on my webhost. Now I'm picking my way through the webserver and am translating this to a web app.

-----

1 point by shader 5938 days ago | link

The nil is the return value of the function. Since the last thing you did was an assignment, is 'nil. It only shows up like that on the repl, which prints both stdout and return values. It is true that printing a newline first would make it show up in the next line, but it doesn't really matter if you're making it into a web service since the nil won't be printed.

-----

1 point by Adlai 5947 days ago | link | parent | on: Questions from a newcomer

Having separate meaning, determined by context, means that you don't have to use as many distinct identifiers in your program. This is especially pronounced if you're trying to use short identifiers, because there are fewer short identifiers that have some intuitive meaning attached to them, and I think it's useful to allow context to alter this meaning.

However, now that I think of it a bit more, making Arc a Lisp-2 would interfere with using compound data in the place of functions. If you have a symbol whose value is a compound type, and that symbol also has a function value, that creates an ambiguous situation. I'll have to suspend my opinions about Lisp-n-ness until I've used Arc for a while.

-----

2 points by pg 5946 days ago | link

Having separate meaning, determined by context, means that you don't have to use as many distinct identifiers in your program.

That would make your code very confusing. Even in Lisp 2s, programmers avoid using the same names for functions and ordinary variables.

Arc3 is getting stable enough that you can probably start with that. Most basic stuff (e.g. everything in the tutorial) should be the same anyway.

-----

7 points by zbeane 5946 days ago | link

> Even in Lisp 2s, programmers avoid using the same names for functions and ordinary variables.

No, they don't.

-----

1 point by Adlai 5944 days ago | link

I'm a big fan of using identifiers such as 'list, 'string, 'fn (isn't a problem in CL, but is one in Arc), 'array, 'char, etc. Often a general name is the most concise and appropriate name to use.

I think that since Arc is supposed to be designed towards flexibility and usability for the programmer, it should have few restrictions, and a common namespace is such a restriction.

However, my opinion on this could change as I explore Arc over the next few weeks.

-----

1 point by Adlai 5947 days ago | link | parent | on: Mappair

AFAIK, the parameter list for fn doesn't do destructuring in Arc. I think it's an easy fix though -- does this work?

   (mac mappair (x y expr args)
     (w/uniq arg
       `(map (fn (,arg)
               (let ,x ,(car  ,arg)
                    ,y ,(cadr ,arg) )
                 ,expr) )
             (pair ,args) ) ) )

-----

1 point by CatDancer 5947 days ago | link

AFAIK, the parameter list for fn doesn't do destructuring in Arc

  arc> ((fn ((x y)) (+ x y)) '(5 10))
  15

-----

1 point by Adlai 5947 days ago | link

My bad. I should start using the language a bit first...

-----

3 points by Adlai 5947 days ago | link | parent | on: Table defaults

What if a default specified at lookup overrode the default specified when the table was created?

My Arc knowledge is essentially #f at the moment, but this

  ((hash-table? fn)
   (ar-nill (or (hash-table-get fn (car args) #f)
                (hash-table-get fn hash-table-default #f))))
seems to be the code for looking up a key using the (some-table key) syntax (i.e. putting the table as the "function" in a form). Would it work to have an optional parameter, so that somebody could call (some-table key default) if they wanted to override the default? I'm thinking that this optional parameter would default to #f if unspecified, and would be passed to the first hash-table-get in the above code. This seems to enable overriding of a pre-specified default.

-----

1 point by skenney26 5947 days ago | link

I like your idea of having a default that can be overridden.

The code above is actually Scheme (which Arc is implemented in). Getting and setting table values in Arc is much less clunky.

-----

1 point by Adlai 5947 days ago | link

Yeah, I can tell it's Scheme because of #f used for false, instead of NIL.

If it's possible to pass an optional parameter to an Arc-style hash-table lookup (one where you use the table name as the function), than that would also allow overriding a default to NIL, because the 'or in that Scheme code would return NIL rather than evaluating the hash-table-get for the original default.

-----