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

Tip: At least for me (on a Mac), running Arc with "[racket|mzscheme] -f as.scm" and then hitting ctrl-C at some point will kill the whole process, instead of dropping to a [Racket|Scheme] prompt from which I can use (tl) to return to the arc> prompt. Replacing the command with "[racket|mzscheme] -i -f as.scm", -i for interactive, fixes this. Much better.

Also, I highly recommend rlwrap. See this thread: http://arclanguage.org/item?id=10

I have added the following to my .bash_profile (probably the Mac equivalent of .bashrc):

  export PATH=/Applications/Racket\ v5.0/bin:$PATH
  alias arc='rlwrap -c -r -l ~/Documents/ARC_LOG -q "\"" -C arc racket -i -f ~/Dropbox/arc3.1/as.scm'
I used to have 'cd ~/Dropbox/arc3.1;' before the 'rlwrap ...' part; the aload function and all the Arc files that load other Arc files (libs.arc) assume that you're in the Arc directory. I worked around this by wrapping all the calls to 'aload in as.scm like this:

  (parameterize ((current-directory (current-load-relative-directory)))
    (aload "arc.arc")
    (aload "libs.arc") 
    
    (aload "sh.arc")) ;my stuff
Now I can call "arc" from any directory; it will load fine and I'll still end up in the directory I started in.

-----

1 point by evanrmurphy 5555 days ago | link

rlwrap really is great. I like it almost as well as having my REPL in Emacs.

Also, that's a neat idea to house your Arc installation in Dropbox.

-----

2 points by waterhouse 5564 days ago | link | parent | on: Try Arc

Suggestion: The Arc toplevel binds the variables "that" and "thatexpr" every time you enter something: the expression typed in becomes thatexpr and the value of the expression becomes that. I think it would be really nice, and also easy, to add that feature.

-----

6 points by waterhouse 5565 days ago | link | parent | on: ([_] 1) vs ((fn (x) x) 1)

[_] = (fn (x) (x)), not (fn (x) x).

  [idfn _] = (fn (x) x).
  ([idfn _] 1) -> 1.

-----

1 point by d0m 5565 days ago | link

Oh my ... thank you. That explains a lot of things :p

-----

3 points by waterhouse 5565 days ago | link | parent | on: The use of + for non-numbers

With regard to (2), to destructively append the list '(1 2 3) to xs, you can:

  (zap join xs '(1 2 3))
"zap join" is several characters longer than ++, but zap has general utility.

I use the string function to concatenate strings. It seems to work identically to +, as long as the first argument is a string. I do give heterogeneous arguments to string usually, and I like seeing that they will clearly be coerced into a string.

I have a couple of ideas.

1. It would be little problem for me if Arc provided + as is and provided a "plus" function that worked only on numbers and allowed me to simply go (= + plus) and proceed normally. Unfortunately, that would break all the functions in arc.arc and so forth that use + to concatenate lists (which includes, among other things, the setforms function, which is used in expand=). It would be really nice if one could "freeze" the variable references in those functions, so that changing the global value of + wouldn't change what "+" in the bodies of those functions referred to.

2. If you use concatenation so much, perhaps we could allocate an ssyntax character for concatenation. & is currently used for andf (experiment with ssexpand to see). We could boot out that usage, or perhaps have "&&" become andf. Good/bad idea? (Precedent: my memory tells me the TI-89 uses & for string concatenation.)

-----

1 point by garply 5565 days ago | link

Regarding your second suggestion, we could also use . instead of &, as that's what Perl and PHP do - feels a little more natural to me. But . might cause me a little mental friction in differentiating between the different uses of . in (. "a" "b") and (do (= a '(1 2)) a.0).

To be honest, I'm still not crazy about the idea simply because I don't need the speed boost and + doesn't seem to cause me to use extra mental cycles when reading my code. I'd be open to it though if the community really wanted it that way.

We could vote and also ask PG what he thinks and then make a decision.

-----

1 point by prestonbriggs 5565 days ago | link

I wouldn't do anything for a speed boost at this stage. Premature optimization and all that.

Preston

-----

1 point by waterhouse 5567 days ago | link | parent | on: The use of + for non-numbers

Aha! That works. Thank you!

-----

1 point by waterhouse 5568 days ago | link | parent | on: Ask: PG and Anti-OO

What you describe is doable with closures, at least as far as your example goes. Your example can be implemented almost word-for-word in Arc as it stands:

  (def Socket (url port)
    (let o (obj on-receive (fn (..) ..)
                send (fn (..) ..))
      (fn (method-name . args)
        (apply (o method-name) args))))
obj is a macro that creates a hash table (you don't need to quote the keys, by the way). So, (Socket url port) will return a function that accepts 1 or more arguments, assumes the first is a method name, looks it up in the hash-table o, finds the corresponding function, and applies it to the rest of the arguments.

If you want to give your object thing internal state that you'll want to modify, use a let.

Here is an example that probably demonstrates most of what you're looking for. It is pretty much cribbed from SICP[0].

  (def Bank-Account (password)
    (let money 0
      (let check-pass [unless (is _ password)
                        (err "Wrong password!")]
        (let o (obj deposit (fn (x pw)
                              (check-pass pw)
                              (++ money x))
                    withdraw (fn (x pw)
                               (check-pass pw)
                               (if (< money x)
                                   (err "Not enough money.")
                                   (-- money x)))
                    check (fn (pw)
                            (check-pass pw)
                            money)
                    change-pw (fn (new-pw pw)
                                (check-pass pw)
                                (= password new-pw)))
          (fn (method-name . args)
            (apply (o method-name) args))))))

  ; In action
  arc> (= x (Bank-Account "meh"))
  #<procedure: Bank-Account>
  arc> (x 'deposit 50 "meh")
  50
  arc> (x 'check "meh")
  50
  arc> (x 'deposit 200 "meh")
  250
  arc> (x 'withdraw 300 "meh")
  Error: "Not enough money."
  arc> (x 'check "meh")
  250
  arc> (x 'deposit 20 "achtung")
  Error: "Wrong password!"
  arc> (x 'check "meh")
  250
  arc> (x 'change-pw "achtung" "meh")
  "achtung"
  arc> (x 'deposit 400 "achtung")
  650
[0]http://mitpress.mit.edu/sicp/full-text/book/book-Z-H-20.html...

-----

1 point by d0m 5568 days ago | link

Exactly, Arc already have OO with closures. However, what I suggest is this:

  (x 'deposit 400 "achtung")
could be instead written as:

  (deposit x 400 "achtung")
So basically, we get rid of the quote in front of deposit and we reverse the first and second parameter.

I find that:

  (let x (Bank-Account "meh")
    (deposit x 50)
    (check x))
Is cleaner then:

  (let x (Bank-Account "meh")
    (x 'deposit 50)
    (x 'check))

-----

3 points by waterhouse 5565 days ago | link

Given the way I defined a Bank-Account, you could implement this rather simply:

  (def deposit (x . args)
    (apply x 'deposit args))
Or, with akkartik's implementation, which I agree is nicer and better:

  (def deposit (x . args)
    (apply x!deposit args))
And you could make another "object" and give it a distinct 'deposit "method" and it would work fine.

So... it seems that one can easily implement these things in Arc. If you have some project in mind for which you want OO stuff, you can just do the things posted in this thread, and tell everyone how it goes if you like.

If you think OO should be put into the core of the language, well, there's a certain resistance to adding features just because someone suggests it. The core would become large, disorganized, and difficult to maintain if we did that. So, you would have to give a convincing argument as to why it's worth putting in there, and a demonstration of how useful it is in some real project of yours is probably the best argument you could give.

-----

3 points by akkartik 5565 days ago | link

Yes that's implementable; I find this even more expressive:

  (x!deposit 50 "meh")
The implementation is simpler than waterhouse's version - just return the obj:

  (def Bank-Account (password)
    (let money 0
      (let check-pass [unless (is _ password)
                        (err "Wrong password!")]
         (obj deposit (fn (x pw)
                        (check-pass pw)
                        (++ money x))
              withdraw (fn (x pw)
                         (check-pass pw)
                         (if (< money x)
                             (err "Not enough money.")
                             (-- money x)))
              check (fn (pw)
                      (check-pass pw)
                      money)
              change-pw (fn (new-pw pw)
                          (check-pass pw)
                          (= password new-pw))))))

-----

3 points by bogomipz 5552 days ago | link

This kind of OO with closures is a fun experiment and looks very elegant at first sight. I love the (x!deposit 50 "meh") version for its simplicity, the use of ssyntax, and the fact that you can pass x!deposit around as a first class function. Thanks to macros, you can of course easily come up with a nice syntax for the definitions:

  (defclass Bank-Account (password)
    (money 0 debt 0)
    (def check-pass (pw)
      (unless (is pw password)
        (err "Wrong password!")))
    (def deposit (x pw)
      (self!check-pass pw)
      (++ money x))
    (def withdraw (x pw)
      (self!check-pass pw)
      (if (< money x)
          (err "Not enough money.")
          (-- money x)))
    (def check (pw)
      (self!check-pass pw)
      money)
    (def change-pw (new-pw pw)
      (self!check-pass pw)
      (= password new-pw)))
However, the approach has some issues in real life use. First, every bank account instance replicates the method table and so takes up more memory the more methods the class defines, and each method is a closure that takes up memory as well. Also, this hash table obviously needs to be built every time an instance is created. Another big problem that follows from the above is that when you add or redefine methods on the class, existing instances are left with the old implementation. And there is no way to implement inheritance here.

I guess it is possible to remedy most or all of those problems by sacrifying methods as closures and instead do:

  (= bank-account-mt
    (obj check-pass (fn (self o pw)
                      (unless (is o!pw pw)
                        (err "Wrong password!")))
         deposit (fn (self o x pw)
                   (self 'check-pass pw)
                   (++ o!money x))
         withdraw (fn (self o x pw)
                    (self 'check-pass pw)
                    (if (< o!money x)
                        (err "Not enough money.")
                        (-- o!money x)))
         check (fn (self o pw)
                 (self 'check-pass pw)
                 o!money)
         change-pw (fn (self o new-pw pw)
                     (self 'check-pass pw)
                     (= o!pw new-pw))))

  (def Bank-Account (password)
    (let o (obj money 0 pw password)
      (afn (method-name . args)
        (apply (bank-account-mt method-name)
               (cons self (cons o args))))))
Again using a macro to improve readability and writability. Adding inheritance is left as an exercise for the reader.

-----

2 points by rocketnia 5552 days ago | link

I'm sure this doesn't surprise you, but here's a quick version of 'defclass that uses a syntax similar to your first example and an implementation similar to your second example:

  (mac defclass (name constructed-fields derived-fields . defs)
    (let mt (sym:string name '-mt)
      `(do (= ,mt (obj ,@(mappend
                           [do (case car._
                                 def  (let (name parms . body) cdr._
                                        `(,name (fn ,(cons 'self
                                                       (cons 'o parms))
                                                  ,@body)))
                                  (err:+ "An invalid 'defclass "
                                         "declaration was "
                                         "encountered."))]
                           defs)))
           (def ,name ,constructed-fields
             (let o (withs ,derived-fields
                      (obj ,@(mappend [list _ _]
                               (join constructed-fields
                                     (map car pair.derived-fields)))))
               (afn (method-name)
                 (fn args
                   (apply (,mt method-name)
                          (cons self (cons o args))))))))))
  
  (defclass Bank-Account (password)
    (money 0)
    (def check-pass (pw)
      (unless (is pw o!password)
        (err "Wrong password!")))
    (def deposit (x pw)
      self!check-pass.pw
      (++ money x))
    (def withdraw (x pw)
      self!check-pass.pw
      (when (< o!money x)
        (err "Not enough money."))
      (-- o!money x))
    (def check (pw)
      self!check-pass.pw
      o!money)
    (def change-pw (new-pw pw)
      self!check-pass.pw
      (= o!password new-pw)))

-----

1 point by bogomipz 5551 days ago | link

Nice, and you even changed it so x!deposit returns a function again! This does of course add some overhead since a closure is constructed every time you call a method, but still.

One thing I'm not quite happy with is that one has to write o!money. Would it somehow be possible to hide the o? Would it be possible to use !money or .money, or does the parser not allow that? And how to pass the hash table from the afn to the methods without polluting their namespaces? It could be done using a gensym, but then it is not possible to add methods to the method table outside defclass.

Perhaps doing something like this:

  (= bank-account-mt
    (obj check-pass (fn (self pw)
                      (unless (is self!ivars!pw pw)
                        (err "Wrong password!")))
         deposit (fn (self x pw)
                   self!check-pass.pw
                   (++ self!ivars!money x))
         withdraw (fn (self x pw)
                    self!check-pass.pw
                    (if (< self!ivars!money x)
                        (err "Not enough money.")
                        (-- self!ivars!money x)))
         check (fn (self pw)
                 self!check-pass.pw
                 self!ivars!money)
         change-pw (fn (self new-pw pw)
                     self!check-pass.pw
                     (= self!ivars!pw new-pw))))

  (def bank-account (password)
    (let ivars (obj money 0 pw password)
      (afn (selector)
        (if (is selector 'ivars)
            ivars
            (fn args
              (apply (bank-account-mt selector)
                     (cons self args)))))))
Then make defclass turn .foo into self!ivars!foo. Another macro could exist for (re)defining methods after the fact:

  (defmethod bank-account steal-money (x)
    (-- .money x))
Or even redefine Arc's def so you could do:

  (def bank-account!steal-money (x)
    (-- .money x))
since (bank-account 'steal-money) is not an atom and 'def could thus recognize it as different from an ordinary function definition.

-----

5 points by waterhouse 5569 days ago | link | parent | on: Macros without 'mac'?

Due to the way eval works, it doesn't touch lexical variables. So:

  arc> (let i 2 (+ i 1))
  3
  arc> (let i 2 (eval '(+ i 1)))
  Error: "reference to undefined identifier: _i"
This isn't specific to Arc, by the way. In Racket and in Common Lisp, it doesn't work:

  > (let ((i 2)) ;Racket
      (eval 'i))
  . . reference to undefined identifier: i
Thus, your version of when will not work the same way as the macro version when you try to do this, for example:

  arc> (time:for i 1 1000 (mywhen '(is i 666) '(prn "Oh no!")))
  Error: "reference to undefined identifier: _i"
Other than that little kink with lexical variables, though, using eval does do pretty much the same thing as using a macro.

One more thing: eval is also much less efficient if you intend to use it repeatedly. By definition, eval performs full syntax analysis every time you use it. In the below example, its argument is a constant, so a smart compiler could theoretically optimize it to run like normal code, but in general its argument would not be constant.

  arc> (= i 0)
  0
  arc> (time:repeat 1000 (++ i))
  time: 2 msec.
  nil
  arc> (time:repeat 1000 (eval '(++ i)))
  time: 145 msec.
  nil
So, I recommend against using eval if you can avoid it. One use case is where you wait for the user to type in an expression, and you evaluate it and print the result.

-----

1 point by ulix 5568 days ago | link

Thank you for the answer, very interesting. Here's some more things I found:

1) actually the "reference to undefined identifier" error can be avoided un-quoting the variables at calling time, as in: (let i 2 (eval­ `(+ ,i 1))) (time:for i 1 1000 (mywh­en `(is ,i 666) '(prn­ "Oh no!")))

2) the last expression is computed in 176 msec, whereas the regular macro 'when' is computed in 2 msec (much faster as you forecasted).

3) these examples don't work in online interpreter Arc Web REPL (http://dabuttonfactory.com:8080/), but they work in TryArc (http://tryarc.org/)

Thanks

-----


For context, by the way, the above is a direct reply to this comment from ac.scm, right before the old definitions of x-set-car! and x-set-cdr!:

  ; decide at run-time whether the underlying mzscheme supports
  ; set-car! and set-cdr!, since I can't figure out how to do it
  ; at compile time.

-----


I did a couple of tests, and stuff that involves a lot of set-c[ad]ring seems to happen 3-4 times as fast. Nice!

  ;All tests done using "racket -i -f as.scm", Racket v5.0
  ;Where x is (n-of 1000000 (rand))
  ;With mpairs
  arc> (time:no:sort < x)
  time: 12408 msec.
  nil
  
  ;Normal arc3.1
  arc> (time:no:sort < x)
  time: 36004 msec.
  nil

  ;This function destructively reverses a list, as in CL
  ; but it's actually slower than 'rev, for some reason
  (def nreverse (xs)
    (and xs
         (let tail nil
           (while xs
             (let rest cdr.xs
               (= cdr.xs tail
                  tail xs
                  xs rest)))
           tail)))

  ;With mpairs
  arc> (time:no:zap nreverse x)
  time: 719 msec.
  nil

  ;Normal arc3.1
  arc> (time:no:zap nreverse x)
  time: 2732 msec.
  nil
On the other hand, it seems to slow down normal list operations somewhat, about 20%.

  ;The format of the output of "time" is "time: nnn msec."
  ; so we will take the output of a bunch of iterations,
  ; read all sexprs, and take the second of every three.

  ;With mpairs
  arc> (map cadr (tuples (readall:tostring (repeat 30 (time:len x))) 3))
  (66 65 63 63 62 62 62 63 66 63 63 69 74 66 63 371 97 92 74 71
   71 71 64 71 63 71 63 63 63 63)

  ;With normal arc3.1
    arc> (map cadr (tuples (readall:tostring (repeat 30 (time:len x))) 3))
  (62 62 61 52 52 52 52 52 52 51 51 51 57 52 52 56 56 61 61 52 52
   52 52 54 56 61 52 52 54 61)
On the whole, probably worth it.

And, by the way, for your amusement and my pleasure, I have a dinky little version of mergesort that runs 20% faster than the destructive version defined in arc.arc runs in arc3.1. It's stable, too. http://pastebin.com/XFz6T9xW

-----

1 point by aw 5575 days ago | link

Thanks for the tests!

On the other hand, it seems to slow down normal list operations somewhat, about 20%

hmmm, since since most Arc lists are now built out of mpairs in this hack, I wonder if it would be any faster to test for mpair's first:

  (xdef car (lambda (x)
               (cond ((mpair? x)   (mcar x))
                     ((pair? x)    (car x))
                     ...
etc.

-----

1 point by waterhouse 5576 days ago | link | parent | on: File I/O Challenge

Excellent. Have added to my personal Arc library (which is a big fat file named "a", to which I keep appending things):

  (mac tofile (f . body)
    (w/uniq gf
      `(w/outfile ,gf ,f
         (w/stdout ,gf
           ,@body))))
  (mac fromfile (f . body)
    (w/uniq gf
      `(w/infile ,gf ,f
         (w/stdin ,gf
           ,@body))))
I'm not sure about the appendfile--given what infile and outfile do, it sounds like a procedure that creates an output-port that appends to a file. Maybe to-appendfile, appendtofile, appendingfile, tofile/append... Alternatively, we could make keyword arguments happen in Arc, and then you would just throw ":append t" or something inside the call to tofile. That would also allow for further extension with, e.g., an :if-exists argument.

-----

1 point by rocketnia 5576 days ago | link

How about 'tolog? Are files opened for appending for other reasons, in practice? This would also keep with the to-means-output, from-means-input pattern.

-----

2 points by fallintothis 5576 days ago | link

I'd try to err on the side of generality. And I'm not quite as concerned about to:output / from:input, if the names are still "clear enough".

As to waterhouse's suggestions, I had considered those names. I suppose if you read appendfile as a noun instead of a verb-and-noun, it's confusing (though infile and outfile don't really have the same problem, so it's not the train of thought my brain follows). It's hard modifying a name like tofile with a long word like append. We already have two words in tofile, so adding a third without hyphenation is stretching it, and adding hyphens breaks the flow with the other names (fromfile, tostring, etc.). We could go for something shorter, like addtofile, which delineates itself well without hyphens because each word is one syllable. If we can't avoid hyphens, using / instead (e.g., tofile/a or tofile/append) flows better, but isn't that great.

Another name that occurred to me -- and is probably my favorite so far -- is ontofile, which is still simple enough to not need hyphens, communicates intent (appending something onto a file), and worms the word to in there, painting it with the to:output / from:input correlation. Thoughts?

-----

2 points by evanrmurphy 5576 days ago | link

Another name that occurred to me -- and is probably my favorite so far -- is ontofile, which is still simple enough to not need hyphens, communicates intent (appending something onto a file), and worms the word to in there, painting it with the to:output / from:input correlation. Thoughts?

+1! ontofile is a great name, in my opinion, for all the reasons you listed.

I searched for a good portmanteau in the vein of mappend, but I don't think there is one. fappend? Sounds like frappuchino. filepend is decent, but I think I prefer ontofile.

-----

2 points by fallintothis 5576 days ago | link

fappend? Sounds like a frappuchino.

Hahaha! Or worse: http://www.urbandictionary.com/define.php?term=fap :P

filepend is decent, but I think I prefer ontofile.

Agreed on both counts. But that's a clever one; I hadn't thought to try out portmanteaus yet.

-----

1 point by evanrmurphy 5576 days ago | link

Oh wow, didn't know that one. Who knew you could get street smarter hanging out on Arc Forum??

-----

1 point by garply 5576 days ago | link

Regarding logging, I use a log function such that (log "my log message here") appends to a globally identified file log*.

-----

More