Arc Forumnew | comments | leaders | submit | conanite's commentslogin
4 points by conanite 6276 days ago | link | parent | on: Objects in Arc

You can use lexical scoping to store private state for an object (not accessible via (= thing!foo 'bar) ), and so that your object's functions can call each other without requiring any kind of qualifying prefix. Here's a contrived example of a "shouter" that can be started, stopped, and shouts stuff when started.

  (def shouter (name)
    (with ((this started) nil)
      (= this (let (start stop shout prname equals) nil
                   (= start  (fn ()  (assert started)))
                   (= stop   (fn ()  (wipe started)))
                   (= shout  (fn (s) (if started (pr (upcase s)))))
                   (= prname (fn ()  (shout name)))
                   (= equals (fn (x) (is this x)))
                   (obj start start stop stop shout shout prname prname equals equals)))
       this))

  arc> (set sh1 (shouter "me"))
  arc> (sh1!start)
  arc> (sh1!prname)
  "ME"
"this" is defined so that 'equals will work (otherwise the functions have no way to reference the object that contains them); 'prname can call 'shout directly. 'name and 'started are hidden - 'name cannot be modified, and 'started can only change via the "public" 'start and 'stop functions.

The right macro can disappear all the boilerplate:

  (def shouter (name)
    (with ((this started) nil)
      (= this (make-obj
         (start  ()  (assert started))
         (stop   ()  (wipe started))
         (shout  (s) (if started (prn (upcase s))))
         (prname ()  (shout name))
         (equals (x) (is this x)) ))
    this))
'make-obj was inspired by obj and http://arclanguage.org/item?id=7387 ("if it's an idiom, it needs a macro") ...

  (with ((make-def double-each) nil)
    (= make-def (fn ((name args . body))
       `(def ,name ,args ,@body)))
    (= double-each (fn (lst)
       (if lst
           `(,(car lst) 
             ,(car lst) 
             ,@(double-each (cdr lst))))))
    (mac make-obj args
      `(with (,(map car args) nil)
             ,@(map make-def args)
             (obj ,@(double-each (map car args))))))
Coming from javaland, I fear I'm trying to hack objects onto arc and failing to see the One True Way with functions. But this macro has been convenient for interfacing with java. I guess some seriously tricky macroing would allow inheritance ... this is left as an exercise for the reader :)

-----

4 points by absz 6276 days ago | link

The real problem with storing the functions in the object, I think, is that you have different copies of the function for every object. There are two problems with this: (a) it wastes space, and (b) if you redefine the function, you have to recreate all your objects. That's the advantage of storing methods externally.

Also, speaking of "if it's an idiom, [it] probably needs a macro," I often find myself writing (obj name1 name1 name2 name2 name3 name3 ...), like you have above. So here's my nobj macro to solve that problem; for the same effect as above, just write (nobj name1 name2 name3 ...):

  (mac nobj args
    " Creates a table from the list of variables passed to it; each variable's
      name is keyed to its value.  E.g. if x = 10 and y = -10, then (nobj x y)
      results in #hash((y . -10) (x . 10)).
      See also [[obj]] [[table]] "
    `(obj ,@(flat:map [list _ _] args)))

-----

3 points by tokipin 6275 days ago | link

well, we can closure the methods with the constructor function so they aren't duplicated, at the expense of explicitly requiring the object to be passed to them, or some kind of dispatch mechanism. we can also implement simple inheritance:

  (def inherits (obj1 obj2)
       (fn (key)
           (or (obj1 key) (obj2 key))))
with (apply or ...) we could have a long inheritance chain, but here i have just two objects so the pattern is clear. we can then define objects like so:

  (let _proto (obj
                full (fn (self) (string self!first " " self!last))
                until (fn (self year) (- year self!age)))
  
    (def person (first last age)
           (inherits (obj
                       _proto _proto
                       first first
                       last last
                       age age)
  
                     _proto))
  
  )

  arc> (= p (person "joe" "momma" 18))
  #<procedure>
  arc> (p!full p)
  "joe momma"
  arc> (p!until p 22)
  4
  
  arc> (= q (person "axe" "murderer" 10))
  #<procedure>
  arc> (q!full q)
  "axe murderer"
  arc> (q!until q 22)
  12
because the inheritance is dynamically dispatched or whatitbe, we can alter the methods with the intended effects:

  arc> (q!backwards q)
  Error: "Function call on inappropriate object nil (#<procedure>)"
  arc> (= ((q '_proto) 'backwards) (fn (self) (string self!last " " self!first)))
  #<procedure:gs2439>
  arc> (q!backwards q)
  "murderer axe"
  arc> (p!backwards p)
  "momma joe"

  arc> (= ((q '_proto) 'until) (fn (self age) "a long time"))
  #<procedure:gs2451>
  arc> (q!until q 22)
  "a long time"
  arc> (p!until p 18)
  "a long time"
note the 'inherits' function is agnostic. we can inherit from arbitrary objects and functions or what have you, just that in this case it was used to inherit from a hidden prototype

anarki supposedly has user definable syntatic sugaries, so the (q!blah q) pattern could be sugarized

-----

2 points by almkglor 6275 days ago | link

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

-----

4 points by conanite 6274 days ago | link

Does it really store a new copy of the function each time? I thought it would only store the closure, and apparently closures are cheap ( http://arclanguage.org/item?id=7342 ) (sorry almkglor, I end up quoting you all the time).

And as for redefining functions: once your api is stable there's probably less need to redefine functions, and if necessary you can still

(= p!until (fn () ...))

(although this way you don't have access to "private" variables in the lexical scope of the original. I'm completely with EliAndrewC in preferring (p!until ...) over (person-until p ...)

And thanks for nobj, it's awesome. It makes my macro look like Visual Basic. Slowly, I learn ...

-----

3 points by almkglor 6274 days ago | link

Yes, a good implementation should store just the closed variables and a reference to the code - there shouldn't be any code duplication.

It thus depends on how many local variables are being closed over. Note that in some implementations (although not in arc2c, and by inference not in SNAP) a closure is just two pointers: a reference to an environment and a reference to the code. Of course each 'let form and function would create a new environment though, and this style is not so often used because lookup of closed variables can require indirection.

-----

2 points by absz 6274 days ago | link

You're welcome---I'm glad to have been of assistance.

As for storing copies, I would have said that it would store extra copies because of the different variable it closes over, but almkglor points out that you can separate code and environment, so the question is what mzscheme does.

The thing about redefinition is that in, say, Ruby, you can do

  class String
    def foo
      code_goes_here
    end
  end
And every string will have that new foo method. Here, you can only redefine the methods of one object.

For me, the real syntax question is whether we want (until p age) (which probably means that we are using the CLOS model of generic functions) or we want (p!until age) (which probably means that we are using the Smalltalk model of message passing). I sort of like the former syntax, but I also sort of prefer the Smalltalk model. What do you think?

-----

1 point by almkglor 6274 days ago | link

Note that redefinition using (p!until age) syntax is still possible in Arc using 'defcall and if you predeclare the private variables.

For instance, consider this:

  (deftype foo (x)
    (private y z)
    (meth niaw ()
      (do-something x y z))
    (meth arf (something)
      (do-something-else something x y z)))
  =>
  (let methods
       (table
         ; lambda lifted!
         'niaw
         (fn (x y z)
             (do-something x y z))
         'arf
         (fn (x y z something)
             (do-something-else something x y z)))
    (def foo-replace-method (s f)
      (= (methods s) f))
    ; so external code can determine the local variables
    (def foo-get-private-variables ()
      '(x y z))
    (def foo (x)
      (with (y nil z nil)
        (let invoker
             (fn (f rest)
               (apply f x y z rest))
        (fn (which-method)
          (aif
            (methods which-method)
               (fn rest (invoker it rest)))))))
Then a method redefining macro can be:

  (def lastcons (l)
    (if (cdr l)
        (lastcons:cdr l)
        l))
  (mac redef-meth (type meth params . body)
    (givens replacer (sym:string type "-replace-method")
            privates (eval:list:sym:string type "-get-private-variables")
            _ (= (cdr:lastcons privates) params)
      `(,replacer ,meth (fn ,privates ,@body))))
Note that foo-replace-method and foo-get-private-variables could be placed in a central global table or two instead.

-----

5 points by almkglor 6276 days ago | link

  (mac nobj args
    " Creates a table from the list of variables passed to it; each variable's
      name is keyed to its value.  E.g. if x = 10 and y = -10, then (nobj x y)
      results in #hash((y . -10) (x . 10)).
      See also [[obj]] [[table]] "
    `(obj ,@(mappend [list _ _] args)))
^^

-----

1 point by absz 6276 days ago | link

Even better, then :) And I definitely need to remember that this exists.

-----

3 points by almkglor 6276 days ago | link

^^ Of course you don't: just tell Arc about what you do remember, and it'll tell you more about related things you might want to look at too:

  arc> (help map)
  (from "arc.arc")
  [fn]  (map f . seqs)
   Applies the elements of the sequences to the given function.
      Returns a sequence containing the results of the function.
      See also [[each]] [[mapeach]] [[map1]] [[mappend]] [[andmap]]
      [[ormap]] [[reduce]]

-----

6 points by absz 6276 days ago | link

I know, but when was the last time you thought you needed help with map of all things? :)

Actually, aha! I added this to the bottom of ~/.arcshrc

  (let func (random-elt:keys help*)
    (prn "Documentation for " func " " (helpstr func)))
Now whenever I start arc, it will print, e.g.,

  Documentation for saferead (from "arc.arc")
  [fn]  (saferead arg)
   Reads an expression, blocking any errors. 
  
  Use (quit) to quit, (tl) to return here after an interrupt.
  arc> 
And thus hopefully I will learn something :)

-----

3 points by almkglor 6275 days ago | link

> 'make-obj was inspired by obj and http://arclanguage.org/item?id=7387 ("if it's an idiom, it needs a macro") ...

Lisp boy: Do not try to abstract the idiom. That's impossible. Instead... only try to realize the truth.

Newb: What truth?

Lisp boy: There is no idiom.

Newb: There is no idiom??

Lisp boy: Then you'll see, that it is not the idiom that does abstraction, but yourself.

^^

-----

2 points by conanite 6286 days ago | link | parent | on: Arc Welder - wannabe IDE

the good news is that it doesn't hang any more on unbalanced parens. In fact, it highlights them in bright red! What i'd like though is auto-insert of right-parens, and auto-surround selected text. textmate does this and it's really nice, it seems dr scheme doesn't. It's really convenient for turning (expr) into (do (expr) ...)

-----

1 point by conanite 6286 days ago | link | parent | on: parser and test runner

how do you use a scanner? Is it

  (car my-scanner)
or

  (my-scanner 'car)
? I've been browsing arki source for examples but I'm completely out of my depth there ...

One aspect of the token generator is that sometimes it recognises two tokens simultaneously. In other words, when it sees the right-paren in

  ... foo)
, it recognises "foo" and right-paren all at once. Perhaps this is the wrong way to do it, and I should be using 'peekc instead. But I suppose I can do this with a scanner:

  (scanner 'car "foo"
           'cdr (scanner 'car right-paren
                         'cdr (tokenize-scanner etc ...)))
I had previously tried modelling each state as a separate sub-function as you suggest, but couldn't get it to work. But that was before I noticed the

  (with ((a b c d) nil)
      (= a (fn () ...))
      (= b (fn () ...))
      (= c (fn () ...))
   ...)
idiom. Gotta try again ...

-----

4 points by almkglor 6285 days ago | link

> I noticed the

  > (with ((a b c d) nil)
  >     (= a (fn () ...))
  >     (= b (fn () ...))
  >     (= c (fn () ...))
  >  ...)
> idiom.

If it's an idiom, probably needs a macro for it then ^^

  (mac with-r (vars . body)
    (let vars (pair vars)
      `(let ,(map car vars) nil
         ,@(map [let (var val) _ `(= ,var ,val)] vars)
         ,@body)))
edit: as an aside, peekc doesn't seem to work properly sometimes ^^

-----

1 point by absz 6239 days ago | link

Nice macro, but why the -? withr seems more consistent with things like withs.

-----

2 points by almkglor 6239 days ago | link

I agree. Should indeed be 'withr, and probably also define a 'givenr just for consistency

-----

2 points by almkglor 6286 days ago | link

How to use a scanner:

  (car my-scanner)
How to construct a scanner:

  (scanner 'car (your-expression)
           'cdr (your-expression))
Note that the expressions in the 'scanner form are delayed, i.e. they are not evaluated until a 'car or 'cdr is performed on your scanner, and they are evaluated only once.

edit: an important note: scanners have the exact read semantics of lists. So simply zapping cdr at a scanner will not advance the scanner, it will only advance the place you are zapping.

There's no need to use 'peekc or similar: all you need is to use stuff like 'cadr, 'caddr.

Because scanners have the exact read semantics of lists, you can use such things as 'each, 'map, etc. Just don't write using scar, scdr, or sref.

If you wanted to emulate lists, you can do something like:

  (def my-cons (a d)
    (scanner 'car a
             'cdr d))
Of course, since a and d are just var references, there's little point in delaying their execution.

edit2: Here's how you might make a generator:

  (def generator (f v)
    (scanner 'car v
             'cdr (generator f (f v))))

  (= b (generator [+ _ 1] 0))
  (car b)
  => 0
  (cadr b)
  => 1
  (cadr:cdr b)
  => 2
'map, 'keep, and a few other functions become lazy when applied on scanners, so you can use an infinite-series generator on them safely

-----

2 points by conanite 6290 days ago | link | parent | on: Response to the Arc Challenge

Markaby (a ruby html-generation framework) did something similar - it would raise an error if you tried giving the same id to more than one element, for example. Unfortunately, it was quite slow, and last time I checked was not being actively developed. But it was so, so, so readable. A lot like almkglor's http://arclanguage.org/item?id=5608

-----

3 points by almkglor 6290 days ago | link

But! But! I didn't actually implement it, which means it doesn't actually count. Code or it didn't happen.

What I did implement is: http://arclanguage.org/item?id=5570

I feel that it's almost as good as the other solution I presented, but I wonder what the opinion of others are. One advantage it has is that it's the non-Arc code that has special syntax, unlike the marcup version where it's the Arc code that has the special syntax.

Apparently the mockup 'marcup is a bit more popular ^^

Would anyone prefer the 'marcup version over the current, existing 'w/html?

-----

3 points by conanite 6291 days ago | link | parent | on: Response to the Arc Challenge

Minor nitpick: the introduction says "HTML and the HTTP protocol were originally designed as a way of presenting, and hyperlinking, static documents" ... but from rfc2616:

  [HTTP] is an application-level protocol for distributed, collaborative, hypermedia information systems.
And from http://www.w3.org/DesignIssues/Architecture.html (Tim Berners-Lee September 1998)

  HTTP was originally designed as a protocol for remote operations on objects, with a flexible set of methods. 
But there's a bigger question: ever since I started writing web apps, I've heard the mantra "keep no state on the server". Arc's continuation or closure thing looks like it's totally breaking the rules. "What about scalability!!", as we say in javaland. So someone's got it all wrong, and why doesn't Hacker News fall over more often?

You mention "unknown or expired link" in arc's scoreboard entry - this is due to arc's appserver harvesting old fnids (function ids). These fnids key into the closures (not continuations) corresponding to the link you clicked. This fnid-harvesting has been the subject of occasional contention on Hacker News ( http://news.ycombinator.com/item?id=169934 ).

At the risk of being totally off-topic with respect to your question: I don't know why the closure approach is always necessary: all the necessary state to vote on a submission or submit an article can easily be stored in the voting link or the submission form. I mean, to produce a HN or arc forum page, the server needs to store 30 separate closures for each article the user might vote on. Which seems a little extravagant. And whatever the benefits to server-side code readability, it results in urls that are completely opaque, which is not always an advantage.

-----

8 points by almkglor 6291 days ago | link

> But there's a bigger question: ever since I started writing web apps, I've heard the mantra "keep no state on the server". Arc's continuation or closure thing looks like it's totally breaking the rules. "What about scalability!!", as we say in javaland. So someone's got it all wrong, and why doesn't Hacker News fall over more often?

As far as I know Hacker News is only one server. Presumably it's pretty well tuned, and besides, there may be lots of hackers there, but I'm sure the readership is much less than, say, friendster, which I'm sure has to solve larger scalability problems.

This may also very well be the reason why Yahoo rewrote Viaweb ^^

> the server needs to store 30 separate closures

Closures are cheap ^^. For example in arc2c, a closure has one cell (cell=4 bytes for 32bit, 8 bytes for 64bit) for type, one cell for length, one cell for function, and one additional cell for each captured variable. Often the captured variables are just one or two, so on a 64-bit system you're looking at 40 bytes for each closure.

On the other hand a string might take up just as much space, and would result in not-so-clear code.

-----

3 points by gidyn 6291 days ago | link

Thank you for pointing out the continuation/closure error - I've updated the site.

Perhaps for HTTP/HTML, it would have been better to say that they were originally used as ...

-----

2 points by almkglor 6291 days ago | link

If you want to be technical anyway a continuation is a form of closure. Mostly the difference is the intent of the closure: if it's meant to be the continuation of control flow, it's a continuation.

-----

3 points by conanite 6290 days ago | link

completely agree - static docs was how I understood the web for its first few years.

-----

5 points by conanite 6293 days ago | link | parent | on: ([+ _ 10]:[* _ 20] 10) = error...

it seems ssyntax only works on symbols (see http://arclanguage.org/item?id=6387 )

-----

1 point by PieSquared 6293 days ago | link

An unrelated question about the ':' syntax: does it really compose functions, or does it work with macros as well?

In other words, can I say

  (macro:function arguments)
or does it only work with functions?

-----

2 points by absz 6286 days ago | link

It's actually in-between:

  (f:g a b c)
becomes

  ((compose f g) a b c)
, which normally wouldn't work if f or g were a macro; however, compose is special-cased in ac.scm so that in the head of a list, this becomes

  (f (g a b c))
. Thus, : only works with macros at the head of a list. You can't do

  (f a b macro:function c)

.

-----

1 point by tokipin 6293 days ago | link

it works with macros as well, which makes for a convenient "modifier" syntax

example: http://arclanguage.com/item?id=4436

-----

1 point by almkglor 6292 days ago | link

The "ultimate" modifier being p-m:

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

-----


rainbow is a completely scheme-independent arc implementation. I expect that it's not the 100-year implementation of the 100-year language: arc2c is likely to have seriously superior performance. And I gather that java isn't pg's all-time favourite language. A lot of scheme leaks into the current arc implementation: (is #f nil) is t, #x12 is 18, 'sread reads scheme expressions. Apart from sread, which is destined in a comment to be replaced by writing 'read, it's not obvious whether these should be considered part of the specification.

In the early days, many java libraries were non-java implementations. Gradually they were replaced by "pure java" versions. Is there any reason arc should not do the same?

-----


an IDE is coming! if you don't mind it being in java, that is ...

-----

1 point by almkglor 6295 days ago | link

How about in Rainbow? Is it implementable there?

-----

4 points by conanite 6295 days ago | link

i'm working on it. hope to post news this weekend. it's also my daughter's birthday, she's 2 tomorrow :)

-----

1 point by almkglor 6294 days ago | link

LOL. Hope it goes well ^^

-----

1 point by oconnor0 6295 days ago | link

Rainbow?

-----

1 point by oconnor0 6295 days ago | link

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

Ah, that Rainbow...

-----

1 point by conanite 6296 days ago | link | parent | on: Lisp-style function definitions

arc lets us write

  (def foo args ...

  (def foo (x y . rest) ...
so that args is the list of all arguments, and rest is the list of all arguments after the first two. I suppose the equivalent scheme-ish way would be

  (def (foo . args) ...

  (def (foo x y . args) ...
I like the fact that arc has no special &rest keyword, that rest args just fall out of the way lists are constructed.

I haven't used scheme so I'm not qualified to comment (but obviously that's not stopping me), but to this n00b it makes sense that the parameter list is distinct from the function name. And as you mention it makes named functions look more similar to anonymous functions. So I have less thinking to do when I'm looking at a parameter list.

-----

6 points by conanite 6299 days ago | link | parent | on: Poll: Priorities

I vote for building real applications and letting those drive the requirements of the language. Only that way can we have a truly minimalist language (if that's still a goal) - everything in the language is required by at least one application. Even performance - yes, I would love some, thanks - isn't so strictly necessary until we build apps that really need it. And then we get performance in the areas that actually matter.

From the itch-theory of open source: pg had an itch for a news forum, I have an itch for a personal-finance-management system, an app to tell me who's borrowed my books, a dedicated arc IDE (@cchooper: I'm trying that in rainbow btw, it's a lot of fun). Oh, and tetris.

I don't have an itch for an arc-xml converter, or an arc sql library, or for a better arc security model or sandbox features. But such libraries might well be important for an application we write, and that would be a good time to implement them ...

-----

5 points by jmatt 6299 days ago | link

My experience shows one real application could easily require many, many libs. Yes there are exceptions, don't bother listing them because the rest of the universal code base relies on libraries.

Libraries are both a weakness and strength for lisps. Yes there are libs that do EVERYTHING out there. Each one has a different style and approach. There are no standard methodologies across the libraries. This makes it difficult. Everytime a developer brings in another library they have to figure out how to use it. Versus interfacing with libraries in python, ruby or even Java / C#... everything is much more uniform.

There is a bigger opportunity with small to medium size experimental projects. I think a great example is: http://www.pageonetimes.com/ which is based on PG's news code.

personal-finance-management system

In the past I've implemented front and back-end financial software. I think it's a huge undertaking and best implemented with as many tested and true libraries as you can find. It's absolutely possible in arc, but why use arc when it's not the best choice (for now atleast). Meanwhile there are a number of other areas where the code will not be nearly as library dependent.

a dedicated arc IDE

I think an IDE is a great idea. In my opinion, the way to do it is to use existing IDEs. DrScheme (already works...), Eclipse, SLIME all come to mind. Now before everyone goes nutty thinking we need our own IDE - yeah eventually. But who is going to put in hundreds of hours of time into implementing a new IDE when you could plug into three existing IDEs in a fraction of the time.

I think at this point a completely new IDE would need to incrementally improve on the best IDEs that are out there. VIM, Emacs, Eclipse, XCode, VisualStudio, etc (ya, a decent IDE seems to be one of the few things M$ does right).

-----

4 points by antiismist 6299 days ago | link

I agree that the lack of libs is a real problem. My real world example: I have have a bunch of RSS feeds, and I want to regularly poll those feeds, extract some content, and add it to my site (pageonetimes.com).

Doing it with Arc alone is too much for me - for mysterious reasons get-http.arc isn't working for me. Even if it did, I would have to write my own rss parser. Instead I am using Ruby for this, and grabbing the RSS content, parsing it, and saving the results is about 10 lines of code using standard libraries.

-----

4 points by stefano 6298 days ago | link

What kind of problems exactly does get-http.arc give you? It's a translation of a piece of Common Lisp code I had written to be able to fetch RSS feeds. The CL version worked well for the task.

-----

1 point by antiismist 6298 days ago | link

Here is what happens:

  arc> (get-request (str->url "http://yahoo.com/"))
  Error: "procedure ...narki/arc/ac.scm:1231:11: expects 2 arguments, given 3: 
  #hash((\"CONTENT-TYPE\" . \"text/... \"LOCATION\" #hash((\"CONTENT-TYPE\" . \"text/..."

(I think the problem lies with me, in that I don't know how to use the library...)

-----

3 points by stefano 6297 days ago | link

I've tried your example and gives me (obviously) the same error, but trying

  (get-request (str->url "http://www.yahoo.com/"))
works. I will investigate further.

BTW, get-request returns a list containing the header of the response and a string containing the page (when it doesn't raise and error, of course).

Edit: bug solved. New version on github!

-----

1 point by antiismist 6297 days ago | link

Thanks you are awesome. So what do you use for RSS parsing?

-----

2 points by stefano 6296 days ago | link

In my CL project I thought to parse it manually, but I've abandoned the project after finishing the HTTP part. I've never tried using it, but you could have a look at treeparse in Anarki, if you haven't done already.

-----

2 points by lojic 6299 days ago | link

If by "personal-finance-management system" you mean an accounting application, I'd recommend GnuCash:

http://www.gnucash.org/

It uses Scheme for its extension language:

http://www.gnucash.org/docs/v1.6/de_DE/t6707.html

-----

4 points by jmatt 6299 days ago | link

personal-finance-management

http://www.mint.com/

-----

2 points by almkglor 6299 days ago | link

> I have an itch for a personal-finance-management system

Interesting. Want me to help you, uh, scratch that? ^^

My mom's an accountant who absolutely hates accounting, which is the main reason I've been fascinated by it^^

-----

More