Hacker Newsnew | past | comments | ask | show | jobs | submitlogin
First Priority: Core Language (paulgraham.com)
47 points by kf on Feb 7, 2008 | hide | past | favorite | 27 comments


That's part of why I focus on code size. Length is an external constraint. If you start looking at code thinking "what is the lower bound on how long this has to be?" you're one step from discovering the new operator that will make it that short.

Paul: Give us a peek into your head... how do you go about discovering new operators? Do you go to set theory or algebra books for inspiration, or look over lots and lots of code with an open mind?

Also, how do you decide if a new operator that ought to be something "easily added" or a part of the language? remove-if-not seems to be something in the former category.


That is a good question. I plan to write about that one day. I'm not entirely sure. I think the process is mainly negative: I look at code that feels bad, in the sense of being unneccessarily long. If you remove the unnecessary bits, what's left is a call to the new operator.

One specific heuristic I have is that I love things that make it possible to put functionality into actual functions that can then be passed as arguments, rather than having it stuck in the middle of a loop. Programs are easier to rearrange that way. So : notation for composition and [..._...] for closures are double wins. They're not only shorter, but also encourage the use of functional arguments.

Maybe triple wins, actually, because they both get rid of variables, which is an especially good way to make programs shorter.

You have to iterate, because sometimes a new operator will make it possible to define more new operators that compress code further. Occasionally a big piece of code just collapses, like cotton candy in your mouth.

I put stuff in the language if I use it a lot in applications. A lot of the operators in arc.arc are on probation, because they seem like they might be useful, but I haven't actually used them much so far.


"... So I want to ask a favor from Arc users: I want to ask for tolerance when I break and change things in what may seem to be arbitrary, small ways. This will make Arc a pain in the ass to write programs on top of, I know, but the end result should be something worth having. ..."

This is the bit that will cause a lot of flack. Is there any way to mitigate code breakage? Is it worth it? Maybe users should confine their coding to sketches in one problem domain re-solving it each time the language evolves. Concentrating on how Arc can be improved instead of just the application.

The best bit I got from this post was re-reading "Design and Research". [0] So the research being done by users, design by pg? Changing the mindset from app builders to language researchers is difficult but not impossible.

[0] http://paulgraham.com/desres.html


> This is the bit that will cause a lot of flack. Is there any way to mitigate code breakage?

Don't distract geese that are laying golden eggs - just stand back and keep very quiet (IMO). I just hope having to deal with this attention and criticism doesn't hurt pg's motivation or the freedom of his frame of mind.


> Is there any way to mitigate code breakage?

unit tests


Why keep the existing primitives? Things like car and cdr were implementation accidents.


From the FAQ:

Why did you keep car and cdr?

Because we couldn't think of any better alternatives. It would have been misleading to use first and rest or head and tail, because conses are fundamentally pairs; lists are one thing you can build with them, but not the only thing.

There's no conventional name in English for the first and second halves of a pair. If you have to make up names, car and cdr are pretty good choices, because they're short and the same length and naturally composable (e.g. cadr).


There's no conventional name in English for the WHAT and WHAT of a pair? :)


Well, you need terms that apply to the first and second halves of a pair as well as the first element in a list and all the rest of the list. First and second work for lists of two items, but they don't feel right to me for longer lists.

Also, they aren't the same length, and that would bother me. I'd prefer left and rite. Or alpha and omega. Or mom and pop. Then you can compose them into mop. Or maybe I'd better leave it to Paul, as I obviously can't be trusted with this kind of thing.


I guess I wasn't really thinking about why choose those names. If it was just that you could have whatever you like - I like (hd x) and (tl x) but that's me. Or even (0 x) and (1: x) could work. A rose is a rose and all that.

What I was really thinking about were things like why have pairs as a primitive and why enshrine the idea of lists as pairs linked up in a certain way?

From ac.scm, it looks like the current set of primitives are:

  (define (ar-type x)
    (cond 
        ((ar-tagged? x)     (vector-ref x 1))
        ((pair? x)          'cons)
        ((symbol? x)        'sym)
        ((null? x)          'sym)
        ((procedure? x)     'fn)
        ((char? x)          'char)
        ((string? x)        'string)
        ((integer? x)       'int)
        ((number? x)        'num)     ; unsure about this                                                                                
        ((hash-table? x)    'table)
        ((output-port? x)   'output)
        ((input-port? x)    'input)
        ((tcp-listener? x)  'socket)
        ((exn? x)           'exception)
        (#t                 (err "Type: unknown type" x))))
So why that set? Why have chars and strings and symbols? Why have integers and numbers?


I suggest FST and RST. These are composable, e.g. FFRRST == CAADDR.


The cond macro, for example. It had all those gratuitous parentheses [...] I wouldn't be surprised if someone had already discovered the Arc trick of collapsing if and cond into one operator

That's done among others by metalua. In AST view, an if-then-elseif-end statement is seen as:

      `If{ cond1, block1,
           cond2, block2,
           ...
           condn, blockn,
           default_block }
Qi also did a great job of cleaning up a lispy syntax. Too bad it hasn't got macros...


>While Arc includes some handy libraries for writing web apps--libraries so powerful, apparently, that it's cheating to compare Arc to other languages using a problem that requires saving a string on the server--that is not the focus right now.

HAHA... great quote, pg!!

As for language features, I think Arc's on the right track. If anything, be flexible and don't resist the urge to change everything. At this stage, the main thing is to get core right and not worry about backward compatibility.


> libraries so powerful, apparently, that it's cheating to compare Arc to other languages using a problem that requires saving a string on the server

It is kind of a straw man though, but he also got a point. Clearly the use of continuations allows very compact descriptions of multi-page control flow. The only languages that can match the succinctness of Arc in the Arc-challenge are other lisps, Ruby and Smalltalk/Seaside, which all of them also uses continuations. Python which is otherwise known for succinctness but which doesn't expose continuations, does not allow such compact code.

On the other hand it is a straw man because you would not use continuation for navigation in real-world production web sites. You would have to store a continuation for every hit on the site forever, which is clearly not scalable. Therefore it is obvious that serious web frameworks uses other approaches more in line with the stateless nature of HTTP, even if it leads to more verbose code.

I still think the arc-challenge is fun (and I agree that brevity is very desirable), but I think it would be more "fair" if it was framed with real world requirements (scalable, pages bookmarkable etc.) rather than random constraints (the user input must not be exposed in the URL) which only serves to disqualify some non-continuation-based frameworks.

Continuation-based web frameworks suffers IMHO from the same mistake as ASP.NET - trying to make web development look like traditional application code. ASP.NET tries to make it look like event-based desktop GUI app development, Arc tries to make it look like a long-running interactive app written in a function style.

What web-frameworks are really trying to, is to create a new kind of DSL which embraces the spirit and constraints of the web rather than try to hide it. We are not there yet, and such a holy-grail web app DSL might very well be written in Arc one day. It would probably be very short then.


OK, I just realized that the example in the Arc-challenge might be possible to implement without the use of closures persisted between requests.

If the page URLs are generated from the expression tree - outer expression is "/said", page 2 is "/said/anonymous1", page 3 is "/said/anonymous1/anonymous1" or something like that, the framework could locate and execute the correct expression for every hit. The framework would have to be smart enough to detect that the "foo" input to the page 2 is used on page 3, so it is passed on in the generated link. This approach could actually be fully scalable.

(But damn! It would be disqualified because the input is passed from page 2 to 3 in the URL. I suppose it would be cheating to encrypt it?)


Hint: many web stacks suppport server-side sessions. Via cookies.


Yeah, but you don't want to keep navigation state in the session. Otherwise you have to keep the session alive indefinitely to support bookmarking. Also, session based navigation state breaks if you browse the site in two tabs at the same time. And it breaks the back-button.


pg's server-side closures are just a variation on the session theme, except that the data being stored is a procedure.

You've just listed a bunch of flaws evident on this very forum . Any link with fnid as a parameter is pointing to a server-side session closure that might expire before you actually click the link.


OK, agreed, I took your "hint" to indicate that you believed the problem could be solved by cookie-based sessions.


"On the other hand it is a straw man because you would not use continuation for navigation in real-world production web sites. You would have to store a continuation for every hit on the site forever, which is clearly not scalable."

I think if you are not using first-class continuation and are rolling your own CPS-style libraries, you can make the continuations serializable. Then you can save them on a LRU cache like memcached, to disk, or pass them to the client, depending on your performance requirements.


Agreed, if you serialize the closure and append it to the URL, you solve the problem. I actually proposed this fix over in the Arcforum yesterday: http://www.arclanguage.com/item?id=1760

But if you keep the closure on the server, whether memcached or on disk, you still have the problem that you have to keep storing a growing amount of closures forever.


I think for most public web sites, keeping sessions in a LRU cache like memcached shouldn't be a problem, since it automatically purges the relatively inactive sessions. Assuming each session uses up 100k, which is a lot if the user is not writing a novel, you can still keep 10,000 active sessions in 1GB of memory. If you think the sessions are purged too early, you can add more ram, or more boxes, since memcached is distributed. Last I checked facebook had a 3TB memcached cluster. But ya, if the sessions are small enough you might as well pass them to the clients.


Sorry, I'm confusing continuations and closures in the parent post. You don't need to persist the call stack to implement the Arc-challenge, it is enough to persist the closure. It still have the same problems, though.


"My first priority with Arc right now is the core language--those operators that are neither primitives like car and cdr, nor special-purpose library functions. I mean operators like CL's mapcar, let, remove-if-not, and so on."

let is a special form (at least in CL) so it's a primitive, no?...


That's funny. I forgot let is a special form in CL. It doesn't really need to be, and it isn't in Arc.

  (mac with (parms . body)
    `((fn ,(map1 car (pair parms))
       ,@body)
      ,@(map1 cadr (pair parms))))
      
  (mac let (var val . body)
    `(with (,var ,val) ,@body))


You can define let in terms of lambda.

    (let ((x 99))
      (foo x))
can expand to:

    ((lambda (x) (foo x)) 99)


I think if this had been written earlier (and instead of the Arc challenge for example) more folks would better understand the actual goal of Arc.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: