Arc Forumnew | comments | leaders | submit | shader's commentslogin
4 points by shader 5410 days ago | link | parent | on: Why so proper, alist?

Here's a rather radical question: Why do lists need to be nil terminated at all? Why couldn't we have the normal (a b) notation mean (a . b), and (a b c) mean (a . (b . c)), etc. Then redefine 'car and 'cdr so that they handle atoms: car of atom is itself, and cdr of atom is nil. That way car and cdr never fail on atoms, and map etc. still terminate properly on lists.

The only bad side effect would be lack of explicit rest args; all functions would by default have a rest arg. However, since lists are no longer specially terminated, the last arg wouldn't necessarily be wrapped in a list if the function was called with the proper number of args.

I know I'm questioning half a century of lisp convention; what painfully obvious flaw am I missing here?

-----

2 points by rocketnia 5410 days ago | link

I've actually heard that suggestion before, somewhere. * searches for it*

Oh, it's a misinterpretation I had of something on the newLISP page:

http://www.newlisp.org/index.cgi?page=Differences_to_Other_L...

  ;; Common Lisp and Scheme
  (cons 'a 'b) => (a . b)   ; a dotted pair
  
  [a | b]
  
  ;; newLISP
  (cons 'a 'b) => (a b)     ; a list
  
  [ ]
   \ 
   [a] -> [b]
Now that I read it again, it's totally not referring to the notation. What it means is most clearly stated at http://www.newlisp.org/downloads/newlisp_manual.html#nil_and...:

The cons of two atoms in newLISP does not yield a dotted pair, but rather a two-element list. [...] There is no dotted pair in newLISP because the cdr (tail) part of a Lisp cell always points to another Lisp cell and never to a basic data type, such as a number or a symbol.

So it's just a matter of not having programmer-managed cons cells. Nothing to see here....

---

Back to your idea, what happens if you have the syntax (a b c (d e f))? Isn't that indistinguishable from (a b c d e f)? Would alists have to be explicitly written as ((a 1) (b 2) (c 3) nil)?

-----

1 point by shader 5410 days ago | link

Good question.

I suppose that a list at the end of a list would have to be paired with nil, so (a b c (d e f)) would actually be the list (a b c (d e f) nil), but that should be possible to handle at the reader/'list level, and wouldn't make much of a difference in the use of the code. If that simple change is made, then your alist example shouldn't need to be explicitly terminated.

-----

1 point by akkartik 5410 days ago | link

If we're relying on reader magic, what's the benefit of making atoms rather than lists nil-terminated?

It seems kinda futzy for the last element to be nil sometimes but not others.

-----

1 point by shader 5410 days ago | link

Yes, it does seem kind of fuzzy. And I doubt it would actually be the reader doing that, more likely the 'list function would be the source for the extra nils

However, you need some way to distinguish between a list in the car, and the next element in a chain. The only way to do that is to have it be in the car of a cons cell, and the only way to do that is to have something else in the cdr. If you don't have anything after the list, you have to fill it with nil.

-----

5 points by rocketnia 5410 days ago | link

The only way to do that is to have it be in the car...

Actually, you could have a special symbol '! in the car in order to identify that the cdr is actually supposed to be the last element. Then (a b c (d e f)) is represented as (a . (b . (c . (! . (d . (e . f)))))), i.e. (a b c ! d e f). Banged lists. ;)

In fact, having the symbol be a colon might even make it a nice optional style:

  (accum acc
    (each x args
      (acc (* 2 x))))
  
  (accum acc :
     each x args :
       acc : * 2 x)    ; reminiscent of Arc's (acc:* 2 x)
It's just that you never get to observe the ': from within code. There might be gotchas when generating code, too: The expression (quote : ) would evaluate to nil, huh? XD

-----

3 points by evanrmurphy 5410 days ago | link

Dear rocketnia,

Some of your ideas are very bizarre. I mean this as a compliment. Please keep 'em coming. :)

Regards, evanrmurphy

-----

3 points by evanrmurphy 5410 days ago | link

Very interesting and radical indeed! ^_^

This is reminiscent of a comment at the bottom of arc.arc:

  ; solution to the "problem" of improper lists: allow any atom as a list
  ;  terminator, not just nil.  means list recursion should terminate on 
  ;  atom rather than nil, (def empty (x) (or (atom x) (is x "")))
It also reminds me of a feature in PicoLisp: symbols evaluate to nil by default instead of raising an undefined variable error.

-----

3 points by akkartik 5410 days ago | link

Scheme flags an error on car/cdr of nil. Common lisp doesn't. Now you're suggesting doing this for all atoms. I like this because it loosens another constraint I hadn't even noticed. (http://arclanguage.org/item?id=13414)

-----

2 points by shader 5405 days ago | link

I think in the end just extending car/cdr to support atoms would still be useful, but changing lists to being nil terminated only some of the time would likely be too inconsistent and/or buggy to risk. It would save some space, but on the whole doesn't really improve much.

-----

2 points by shader 5419 days ago | link | parent | on: Noob question.

Me too. This is one of those cases where I keep wishing we had first class macros.

-----

1 point by evanrmurphy 5419 days ago | link

I heard pg & rtm tried first-class macros but the performance was unacceptable. I would really like to have tried them out, seen results from some performance tests... or something!

How difficult of a hack would it be to give Arc first-class macros again?

-----

3 points by aw 5418 days ago | link

If you want to try out first-class macros to see what they could do for you, that's easy enough: write an interpreter for Arc. It'd be slow of course, but enough so that you could try out some different kinds of expressions and see if you liked what you do with it.

-----

1 point by evanrmurphy 5418 days ago | link

Yes. I'm actually taking a similar approach with more instant gratification: try using a lisp that already has them. http://picolisp.com/5000/-2.html

-----

2 points by rocketnia 5418 days ago | link

I was a fan of fexprs not too long ago, and I still kinda am, but they lost their luster for me at about this point: http://arclanguage.org/item?id=11684

Quoting from myself,

Quote syntax (as well as fexprs in general) lets you take code you've written use it as data, but it does little to assure you that any of that computation will happen at compile time. If you want some intensive calculation to happen at compile time, you have to do it in a way you know the compiler (as well as any compiler-like functionality you've defined) will be nice enough to constant-propagate and inline for you.

I've realized "compiler-like functionality you've defined" is much easier to create in a compiled language where the code-walking framework already exists than in an interpreted language where you have to make your own.

If part of a language's goal is to be great at syntax, it has a conflict of interest when it comes to fexprs. They're extremely elegant, but user libraries can't get very far beyond them (at least, without making isolated sublanguages). On the other hand, the dilemma can be resolved by seeing that an fexpr call can compile into a call to the fexpr interpreter. The compiler at the core may be less elegant, but the language code can have the best of both worlds.

This is an approach I hope will work for Penknife. In a way, Penknife's a compiled language in order to support fexpr libraries. I don't actually expect to support fexprs in the core, but I may write a library. Kernel-style fexprs really are elegant. ^_^

Speaking of such Kernel-like libraries, I've thrown together a sketch of a Kernel-like interpreter written in Arc. It's totally untested, but if the stars have aligned, it may only have a few crippling typos and omissions. :-p https://gist.github.com/778492

Can't say I'm a fan of PicoLisp yet, though. No local variables at all? Come on! ^_^

-----

1 point by evanrmurphy 5418 days ago | link

> Can't say I'm a fan of PicoLisp yet, though. No local variables at all? Come on! ^_^

Hmm... not sure what you mean by this. I can define a local variable at the PicoLisp REPL using let just as I would in Arc:

  : x   
  -> NIL
  : (let x 5
      x)  
  -> 5
  : x     
  -> NIL
The rest of your comment was really interesting. Thanks for the links!

-----

1 point by rocketnia 5418 days ago | link

Sorry, I spoke too soon. I could tell 'let and function arguments would be possible, but I was a bit put off by http://software-lab.de/doc/ref.html#conv. From http://www.prodevtips.com/2008/08/13/explicit-scope-resoluti..., it sounds like dynamic scope. Is that right? (I'm not in a position to try it out at the moment. >.> )

Speaking of speaking too soon, I may have said "user libraries can't get very far beyond [an fexpr language's core syntax]," but I want to add the disclaimer that there's no way I actually know that.

In fact, I was noticing that Penknife's parse/compile phase is a lot like fexpr evaluation. The operator's behavior is called with the form body, and that operator takes care of parsing the rest, just like an fexpr takes care of evaluating the rest. So I think a natural fexpr take on compiler techniques is just to eval code in an environment full of fexprs that calculate compiled expressions or static types. That approach sounds really familiar to me, so it probably isn't my idea. :-p

-----

1 point by evanrmurphy 5418 days ago | link

No harm done. :) PicoLisp appears to have lexical scoping but dynamic binding, although my PLT is too weak to understand all the implications of that. From the FAQ:

> This is a form of lexical scoping - though we still have dynamic binding - of symbols, similar to the static keyword in C. [1]

> "But with dynamic binding I cannot implement closures!" This is not true. Closures are a matter of scope, not of binding. [2]

---

[1] http://software-lab.de/doc/faq.html#problems

[2] http://software-lab.de/doc/faq.html#closures

-----

2 points by rocketnia 5418 days ago | link

Sounds like transient symbols are essentially in a file-local namespace, which makes them lexically scoped (the lexical context being the file!), and that transient symbols are bound in the dynamic environment just like internal symbols are. So whenever lexical scope is needed, another file is used. Meanwhile, (====) can simulate a file break, making it a little less troublesome.

-----

1 point by evanrmurphy 5418 days ago | link

But the let example I gave a few comments ago didn't use a transient symbol. Why does it work?

I chatted with PicoLisp's author, Alexander Burger, yesterday on IRC. If I catch him again, I can ask for clarification about the scoping/binding quirks.

-----

2 points by rocketnia 5418 days ago | link

I think it works because while you're inside the let, you don't call anything that depends on a global function named x. :) That's in the FAQ too:

-

What happens when I locally bind a symbol which has a function definition?

That's not a good idea. The next time that function gets executed within the dynamic context the system may crash. Therefore we have a convention to use an upper case first letter for locally bound symbols:

  (de findCar (Car List)
     (when (member Car (cdr List))
        (list Car (car List)) ) )
;-)

http://software-lab.de/doc/faq.html#bind

-----


What we really need is our own reader for arc, so that ssyntax isn't limited to intra-symbol, but can be used between other tokens as well, such as lists and strings.

-----

1 point by evanrmurphy 5424 days ago | link

Can you help me understand why Racket's reader couldn't be extended to do this? It seems very extensible [1]. I've already gotten good mileage out of hacking it with aw's extend-readtable [2]. (Come to think of it, I should continue playing around with this to try and implement the JavaScript-style dot operator.)

I also wonder if rntz's arcc might be useful here [3]. (I haven't tried it yet.)

---

[1] http://docs.racket-lang.org/guide/hash-reader.html

[2] http://awwx.ws/extend-readtable0

[3] http://arclanguage.org/item?id=11128

-----

2 points by aw 5423 days ago | link

The reader is implemented in Arc in the runtime project I'm working on (https://github.com/awwx/ar).

-----


html.arc isn't that long, you could probably rewrite it using strings pretty quickly if you wanted to. And I'm sure that others using arc would be happy to have a string based system if you wrote it. There's no reason we can't have two alternate methods of generating html in arc.

-----

3 points by hasenj 5433 days ago | link

Here's what I cooked up during the past coupla hours.

It doesn't do much, but builds a base for writing composable html elements.

It reuses the 'tag macro from html.arc as a base (no need to rewrite that part) but captures the output in a string (using 'tostring, of course). Thus the 'btag function becomes the base to build and compose html tags.

The few extra functions included serve as example of how to compose tags.

For example, 'hstack stacks its arguments horizontally using table columns.

'prn expressions are interspersed in between code blocks. They serve as examples, and I was using them for debugging.

    (def listify (arg)
         (if (acons arg) arg (list arg)))

    (def btag (tagspec content)
         "Low level tag function, takes two arguments: tagspec and content
         content can be a list or an atom"
         (let content (listify content)
           (tostring (eval `(tag ,tagspec (pr ,@content))))))

    (def element (tagspec . content)
         "Simple element, just a convenience wrapper around btag"
         (btag tagspec content))

    ; alias
    (= e element)

    (def section content
         (btag 'div content))

    (def inline content
         (btag 'span content))

    (prn (element 'div "Hello"))
    (prn (element 'div "Hello " "World"))
    (prn (element 'div "Hello" (element 'span "World")))

    (prn (section "Hello" (inline "World")))

    (def vstack args
         "Stack things vertically"
         (string:map section args))

    (def hstack args
         "Stack things horizontally"
         (btag '(table cellspacing 0 cellpadding 0)
            (btag 'tr
               (map [btag 'td _] args))))

    (prn "Testing vstack and hstack")
    (prn (hstack "hstack" (section "hello") (inline "world")))
    (prn (vstack "vstack" (section "hello") (inline "world")))

    (def kls (classes . content)
         "Generates a div with the classes given in 'classes'"
         (let classes (string:intersperse " " (listify classes))
         (btag `(div class ,classes) content)))

    (prn (kls 'big "Hello " "world"))
    (prn (kls '(small big) "Hello " "world"))

-----

2 points by shader 5433 days ago | link | parent | on: Multi-arg anonymous functions broken?

What is it that you want that code to do? I'm not sure I see why that's a bug. You seem to be providing two arguments, 0 and 1, to a function that only expects one, '_ .

-----

2 points by evanrmurphy 5433 days ago | link

The OP is presuming that anarki hacked the bracketed functions to accept multiple arguments. I remember reading that somewhere as well, but since I haven't tried using the feature before I don't really know.

-----

2 points by shader 5433 days ago | link

If that is the case, then he needs to use the _ and _1, instead of _.0 and _.1 to access the separate arguments, if I remember correctly.

_0 and _ are synonymous, I think.

-----

1 point by garply 5432 days ago | link

Thanks, it seems like my brain was scrambled last night. _0 and _1 (or _a and _b) are the appropriate pieces of code. But the real problem was that I was operating out of an arc directing that was lacking a load/ directory. Thus I was missing the make-br-fn.arc file. Fixed now. Thanks guys!

-----

1 point by garply 5432 days ago | link

It works, although I'm still not sure exactly where the _0 _1 or _a and _b components are implemented.

-----

2 points by shader 5434 days ago | link | parent | on: Tips on implementing arc

"I started Jarc by writing an s-expression reader. Then I implemented the 9 Arc primitives in Java and then I started writing eval in Java. Then I incrementally added the missing global functions used in arc.arc - that took a while, but wasn't particularly difficult."

That's how I planned to start the implementation, but I've also considered writing a compiler that translates the arc code into MSIL using Reflection.Emit. That would allow me to do tail-call optimization, and possibly CPS.

So what do you have currently for your Arc/Java interface, and what are your thoughts on difficulty of implementation, ease of use, etc. associated with that choice? Are there other options that you considered and discarded, or haven't gotten around to implementing yet? I was thinking of trying to treat assemblies, namespaces, classes, and objects like hash tables, and letting (obj 'name) access the member, but I don't know how well that will work with mostly static typing.

I'm not sure what to think about fractional numbers. I don't think I've ever had a good reason for using them, so I won't bother implementing them myself until I do.

-----


This is so similar in concept to what I'm doing with named pipes that I figured I ought to share it now:

First of all, you can create a named pipe in a directory with the command "mkfifo name".

Then, I have a shell script in mzscheme (should probably update to Racket soon) for launching arc that sets up the long running process with normal output redirected to a log file and a second repl redirected to two named pipes "in" and "out". The important part is

  (parameterize ((current-output-port (open-output-file "out" 'update))
                 (current-input-port (open-input-file "in"))
                 (current-error-port (current-output-port)))
                (file-stream-buffer-mode (current-output-port) 'none)
                (tl))
which could be written either in arc or Racket. In arc, it would probably be:

  (w/stdin (infile "in")
    (w/stdout (outfile "out")
      ($:tl)))    ;whatever method you use to get to racket and call the repl
I'm not sure if that would work as well, since as far as I know there is no method in arc for setting the filestream buffer mode to 'none. If you have any issues, just use the racket version above.

Anyway, once you have the pipes set up, you can echo and cat them like any other file. The main difference being that they are treated exactly like a normal repl, and shouldn't have the overhead of sleeping and reading/erasing a file. I use a shell script to connect to them as a repl most of the time:

  #! /bin/bash
  
  bash -c "cat <out &
  cat >in"
Pretty simple, and it gets the job done.

-----


I'm not sure exactly what you mean by "advanced solution."

What were you thinking of? How would you like to see it work? I'm sure we can figure out how to make it happen.

-----

1 point by shader 5440 days ago | link | parent | on: Tips on implementing arc

That's true. Any decisions affecting the core and the reasoning behind them would be useful at this point.

In fact, it would be really interesting to hear more from pg explaining some of the cryptic comments in the original source and some of the deviations of the current arc from the designs that he outlined in his earlier essays. If the explanation for most of them turns out to be "it was easier" or "it looked better" that's ok, I just wonder some times whether shortcuts and aesthetics explain the difference, or whether there were some deeper, hard-earned insights involved.

-----

1 point by akkartik 5440 days ago | link

At least for nil vs () and a couple of other issues, I've tramped up and down over the territory and reassured myself that there aren't any huge subtleties. I've also tried to ensure (github, unit tests) that any subtleties I find are exposed to anybody following.

Arc really is that simple :)

-----

2 points by shader 5440 days ago | link | parent | on: Tips on implementing arc

"I wouldn't build lisp atop a non-lisp (buggy, ad hoc, informally specified, etc. etc.) runtime."

Lisp-on-lisp can be just as ad-hoc, informally specified, and buggy. I see no real reason that arc needed to be implemented at all. After all, it's "just scheme" with a few simple aliases right? I'd argue that arc is also pretty informally specified, and sometimes even buggy. Does that prevent us from using it? No. Which tools are used for developing software has little to do with how well it is written. Yes, there is a very high probability that my lisp will be all of the above, but the line you are quoting is specifically about unintentional implementations of lisp, not intentional ones. Unless you're claiming that all other non-lisp platforms are buggy ad-hoc and informally specified, in which case I don't really know what to say.

Really, I'm just taking a long time to say that not all lisps are written in lisp, and that being written in lisp is neither necessary nor sufficient for a good lisp implementation.

"somehow libraries alone don't seem like a compelling enough reason"

Libraries can be a very compelling reason when deciding which language to use. And since I mainly want to write a language that I can use, having good library support is a wonderful feature. Don't get me wrong, I love arc and its easy hackability, but several times I've tried to do something in arc for which the library support just isn't there or is somewhat deficient, and then there's not much I can do about it. Having library support can make a big difference in ease or even possibility of writing a given application. The arc challenge without arc support for web development would be a lot trickier, but an arc challenge without arc support for sockets would be nigh impossible without writing external code.

However, libraries aren't the only reason I'm writing for a non-lisp platform. I also consider it to be an interesting programming exercise, and it puts more of the system under the programmers control. Right now in arc the reader and similar low level pieces are mostly unavailable for hacking, since it's all written in scheme. As such, most of us end up using something like rlwrap for the shell, and overloading ssyntax for all of our reader modification.

As good as those reasons sound, I am not trying to implement arc on .net because I think it will improve arc. Rather, I'm implementing arc on .net because I think it will improve myself as a programmer, and .net as a platform. As far as I know, there are no implementations of lisp on .net that are even remotely usable, while on the JVM there is at least Clojure as well as half a dozen arc implementations. If I want to do any lisp based application development for .net and DirectX in particular, at this point I pretty much have to write it myself.

-----

2 points by akkartik 5440 days ago | link

I am not trying to implement arc on .net because I think it will improve arc. Rather, I'm implementing arc on .net because I think it will improve myself as a programmer, and .net as a platform.

Yeah that makes sense. I certainly didn't mean to discourage you or anything.

I see no real reason that arc needed to be implemented at all. After all, it's "just scheme" with a few simple aliases right?

_Precisely_ the point of wart. I want to maximize the 1-1 correspondence between arc and common lisp to minimize the work I have to do. I'd love to get you to take a look at the code and tell me what you think.

I wasn't claiming lisp implementations should always be atop lisp (clearly impossible), or that lisp implementations can't be buggy. Venturing down the long hard road of dealing with an immature runtime is just not for me, that's all :) (I was a systems programmer in a past life.)

having good library support is a wonderful feature

I find I can often build what I need. Or I can leverage the FFI. Or I can setup a server to provide services from other languages. All those seem relatively painless in lisp, and between them seem to cover all use cases. This is how I stay on easy street where the views are nice and the demons have been banished.

-----

More