Arc Forumnew | comments | leaders | submit | hasenj's commentslogin
1 point by hasenj 5412 days ago | link | parent | on: Scoping bug?

Let itself is just a macro,

  arc> ((fn(tag) (prn tag!bb)) (obj bb 10))
  <quote></quote></quote>
  "</quote>"
Here were wrote a function which took an argument named 'tag, and yet the interpreter still thinks tag is a macro, when clearly it's not a macro in this scope.

If we were to wipe it first, then it would work as expected:

  arc> (wipe tag)
  nil
  arc> ((fn(tag) (prn tag!bb)) (obj bb 10))
  10
  10
If this is not a bug, then it's a design flaw. It means you have to be super careful when choosing names for function arguments, specially if you write a function taking a function or hashtable argument.

  (let name (some-function-generator)
    (name arg1 arg2 arg3))
What if name is defined as a macro by someone, somewhere?

You just defined it as a function, but the interpreter still thinks it's a macro. How can this not be a bug?

-----

2 points by evanrmurphy 5412 days ago | link

> Here were wrote a function which took an argument named 'tag, and yet the interpreter still thinks tag is a macro, when clearly it's not a macro in this scope.

Another way to think about it is that arc does have scope, but it doesn't apply to macros. Macros can only be defined globally (you can't write a local macro), and they're expanded before any of the normal scoping rules come into play.

> What if name is defined as a macro by someone, somewhere?

Yes: it is something you have to worry about and it is arguably kludgy. But again, it's only for macros. If you're overriding globally defined functions or variables of any other type, it works.

> You just defined it as a function, but the interpreter still thinks it's a macro. How can this not be a bug?

It's a thoroughly-discussed design choice that some people don't like. You already linked elsewhere in this thread to aw's proposal of how to change it if you want to give it a try. [1]

---

[1] http://arclanguage.org/item?id=11697

-----


> I think CoffeeScript is wonderful. If only it had macros we'd be set.

It is.

I'm not sure about macros, but having something similar to s-expressions would be nice.

The only thing still kinda holding me back is html templating. Doing them in {{ templates }} like that was a horrible experience with Django. Jinja2 made it a bit more bearable, but {% endtag %} kind of stuff is still bad.

-----

1 point by evanrmurphy 5418 days ago | link

> I'm not sure about macros, but having something similar to s-expressions would be nice.

Yes, s-expressions too. But what aren't you sure about regarding macros. (And what do you see as the benefit of s-expressions besides macros?)

> The only still kinda holding me back is html templating. Doing them in {{ templates }} like that was a horrible experience with Django.

Are you referring to http://mustache.github.com/ ?

-----

1 point by hasenj 5418 days ago | link

> what do you see as the main benefit of s-expressions besides macros?

For this specific problem (html generation), the advantage of s-expressions is you can build a tree with it, with minimum boilerplate. For example, the closing tag is just a ')'. Also, again for this specific problem, because we're mostly just generating strings, just having functions would probably suffice.

> Are you referring to http://mustache.github.com/ ?

Yea. I was looking for html templating system for Node, and that was about the only thing that came up.

-----


I'm under the impression that html.arc is based on hacks that PG used while doing viaweb in common lisp.

If so, then this way of building html is probably a performance hack.

> If everything returns strings, it's nontrivial to make your functions composable.

Really?

> You can't stack them on top of each other unless they take strings as their arguments as well.

Exactly.

Isn't that how all html templating schemes work? A python example is Jinja macros[1]

Composing html elements as functions that take strings and return strings is the only way that makes sense to me.

> But then you can only stack them on top of each other, not treat them as user-facing functions. So you have to keep at least a couple groups of functions with strictly different roles. Perhaps you've already thought of this.

I'm not really sure what you mean.

[1] http://jinja.pocoo.org/templates/#macros

-----

5 points by thaddeus 5418 days ago | link

Personally I like how it currently works:

1. String operations are costly, printing to stdout is not. ie. if I need to run a function to generate some numbers, dumping them at the right time to stdout has very little overhead, having to weave them within a string operation costs so much more.

2. There's a benefit having a web server that pushes changes out to the browser incrementally via stdout. The user doesn't have to wait for the entire operation to complete to see the results. ie, what if the last half of your server operation, provides no output for half of your users?

3. Adding on to #2, for troubleshooting and iterative development purposes, it's nice to see a portion of the output within your browser to see how far a long your operation got, results wise, before it hit an error.

-----

2 points by hasenj 5418 days ago | link

Sounds like a case of sacrificing expressiveness for performance. Somehow I think this goes against the design principles of arc.

For #2 and #3, the output has to be so huge before you reap this benefit. Most apps don't have this property, and if they did, I'd think there's a deeper design problem. Such problems can be better solved using asynchronous javascript requests (aka ajax).

-----

3 points by thaddeus 5418 days ago | link

> For #2 and #3, the output has to be so huge before you reap this benefit.

I think it's, more so, a case of how complex your code is rather than how big your output is. I can have 200,000 lines of code that outputs 20 small numbers. Knowing it hit the 8th number and what that number is can be huge for both a user and for development.

Also - maybe it's just me, but having partial output has helped me with 20 lines of code and very little output.

> Sounds like a case of sacrificing expressiveness for performance.

Also - maybe it's just me, but I've been spending most of my time with Clojure, where the Ring web server requires a string for an output. I found my code became less expressive than arc.

So, for Clojure, I actually wrote my own html framework to mimic arc's functions that write to stdout, then just put a big wrapper on it at the end: (with-out-str (println "stuff")). Next I plan to see if I can hack Ring@Jetty to pipe the output too.

Kinda funny, I went to Clojure and did the opposite of you. :)

-----

1 point by hasenj 5417 days ago | link

Well then, back to my original question: how do you compose elements together?

-----

3 points by thaddeus 5415 days ago | link

So I gave it another whirl and here's my attempt to capture the essence of you're problem:

For example you would like to do this:

  [1] arc> (spanclass "links" 
             (string "use this link:" 
               (tag (a href "http://mydomain.com/the-place-to-go") "here"))
And have it return this:

  <span class="links">use this link:<a href="http://mydomain.com/the-place-to-go">here</a></span>
and your first attempt might be something like this:

  [2] arc> (spanclass "links" 
             (pr:string "use this link:" 
               (tag (a href "http://mydomain.com/the-place-to-go")(pr "here")))
only you find it returns the wrong results:

  <span class="links"><a href="http://mydomain.com/the-place-to-go">here</a>use this link:</span>
so now you're probably thinking by having functions return strings like this:

  [3] arc> (tag (a href "http://mydomain.com/the-place-to-go") "here")
  "<a href=\"http://mydomain.com/the-place-to-go\">here</a>"
then the original function [1] would have worked.

Instead, with arc, you need to approach your code differently. You need to think about the timing of when things are happening rather than having available 'string-things' that you can compose by nesting your functions.

So with arc [1] needs to become [4]:

  [4] arc> (spanclass "links" 
            (pr "use this link:")
            (tag (a href "http://mydomain.com/the-place-to-go")(pr "here")))

  <span class="links">use this link:<a href="http://mydomain.com/the-place-to-go">here</a></span>
Am I capturing it correctly? Does this answer your question on how I compose my functions?

-----

1 point by thaddeus 5417 days ago | link

I've re-looked into your original questions, in an attempt to provide a meaningful response, but I find the scenario's are not concrete enough.

For example I find the re-arrange function a little vague.

i.e. could you not:

  (def something (a b c)
      (output b c a))
Could you provide an real-case like example where you feel you can show a clear difference? For, I found, even your row example can easily work with stdout inside a function rather than using pg's macro. ie. Not liking how some of the existing functions/macros work doesn't mean string weaving is the answer.

And row is a pretty crappy example, even when I built my Clojure library, I ditched pg's implementation and went with a more useful implementation, yet it still uses stdout. You have to remember that pg only built those macro's to support his specific cases in his HN app.

Also, for > Too many tricks. Too clever.

Well it's a library, it's not expected you're crafting macros for your regular coding. I mean there's only so many HMTL cases you need to handle right? So if the library is complete, providing (macro's or not) succinct code and faster results, then it's probably good to have - tricks inside or not.

-----

1 point by shader 5418 days ago | link

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 5418 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 hasenj 5428 days ago | link | parent | on: Command line arguments

Is there a reason as.scm assumes all arguments are script files?

    ; command-line arguments are script filenames to execute
    (for-each (lambda (f) (aload f)) args)))

If no, I'd like to scratch that,

    ; only the first argument is a script filename 
    (aload (car args))))

-----

2 points by fallintothis 5427 days ago | link

Well, blame (https://github.com/nex3/arc/blame/5ac5d567bce08004c0dce6fc4c...) tells us that the commit (https://github.com/nex3/arc/commit/5ac5d567bce08004c0dce6fc4...) wasn't really belabored. My guess is that there's no particular rationale. It does make more sense to me to pass in argv parameters rather than just aloading everything.

-----

2 points by hasenj 5434 days ago | link | parent | on: Ask: what does annotate do?

So, what can you do with it?

Can you make it behave a certain way with pr and + and string, for example?

If yes, then how?

If no, then what's the point? I mean, if you can't overload existing operators to work with the new type, what's the use?

-----

2 points by fallintothis 5433 days ago | link

Not to be snarky, but I linked to http://www.paulgraham.com/ilc03.html for a reason. One salient point (that's independent from annotate):

  arc> (let orig +
         (def + args
           (if (all acons args)
               (do (prn "Blah blah, special, blah blah")
                   (apply orig args))
               (apply orig args))))
  *** redefining +
  #<procedure: +>
  arc> (+ 1 2 3)
  6
  arc> (+ '(a b c) '(d e f))
  Blah blah, special, blah blah
  (a b c d e f)
  arc> (+ '(a b c) 1 2 3)
  Error: "car: expects argument of type <pair>; given 1"
The only things you might have trouble extending this way are macros (since any definition up to the point where you redefine it will have been expanded)

  arc> (mac m (x) `(do (prn "orig") ',x))
  #(tagged mac #<procedure: m>)
  arc> (def f () (m 5))
  #<procedure: f>
  arc> (let orig (rep m)
         (mac m (x)
           (if (number x)
               `(do (prn "redef") ',x)
               (orig x))))
  *** redefining m
  #(tagged mac #<procedure: m>)
  arc> (m "a string")
  orig
  "a string"
  arc> (m 5)
  redef
  5
  arc> (f)
  orig
  5
and names treated specially by the compiler (if, fn, quote, quasiquote, unquote, unquote-splicing, assign, nil, t).

Also relevant: http://awwx.ws/extend0.arc, http://awwx.posterous.com/defrule.

-----

1 point by akkartik 5433 days ago | link

Are you suggesting we don't need annotations for anything that extend works with? That isn't how I read that essay at all.

-----

1 point by fallintothis 5433 days ago | link

I meant that

  (let orig f
    (def f args
      ; you can put ANY code you want in here
      ))
Hence, extend is independent from annotate: just as you can annotate without using extend, you can extend without using annotate. extend is just a byproduct of closures and first-class functions. Of course, combining annotate and extend lets you dispatch based on custom types.

-----

1 point by rocketnia 5433 days ago | link

In official Arc, there's not much of a point. As akkartik says, tagged types are used to designate macros, but that's about the only thing that cares about them.

The slight benefit is that 'annotate is the single most obvious way to do type labels in Arc, meaning people don't usually have to invent their own representation for dynamically typed values. As we explore future directions of the Arc language, we can compare type ideas 'annotate, and in many cases we can implement them using 'annotate too, in order to try them out right away, in a way that's often compatible with other people's code.

In other words, 'annotate only has the value we Arc users give it, but it's there, so we occasionally do add value to it.

-----

2 points by akkartik 5434 days ago | link

Yes types are underused in baseline arc. I think they're only used to distinguish macros from other functions.

I made queues tagged types in anarki, and overloaded len to work with queues.

pr is a good suggestion. Let me know if you have others.

I've thought about overloading +, but couldn't find a clean way since + is implemented at the scheme level. There also seems to be a consensus that overloading + for concatenation is a bad idea, so it doesn't seem worth changing.

-----

3 points by rocketnia 5433 days ago | link

There also seems to be a consensus that overloading + for concatenation is a bad idea

I thought there was a majority for concatenation. XD Well, either way, I'm for it. Maybe you've heard this all before, but I'll summarize:

I've convinced myself that '+ is a sensible standard name for monoid operations, including concatenation, and that '+ and '* together should describe a field whenever that makes sense (http://arclanguage.org/item?id=12850). To appease people who are concerned with efficiency, we can always leave in the math-only version of '+ as a separate variable, or just have the '$ drop-to-Racket operator so they can get it themselves.

As far as implementation goes, the technique I recommend is making a separate two-argument version of the varargs function, so that it's easier to extend consistently (preserving the left-associativity of the varargs version). Then a double dispatch technique comes in handy, but it only has to be a simple one. I've posted some rough examples before to illustrate this strategy: http://arclanguage.org/item?id=12561 http://arclanguage.org/item?id=12858

-----

1 point by akkartik 5433 days ago | link

"I thought there was a majority for concatenation."

I was thinking of http://arclanguage.org/item?id=12347, where it seemed like PG and everyone else didn't like '+ for concatenation :) The thread on matrices and vectors was a separate issue. I think we're all ok overloading + for adding different kinds of mathematical entities, and so on. I'm not sure what sort of monoid allows + for list concatenation.

-----

2 points by rocketnia 5433 days ago | link

I was thinking of http://arclanguage.org/item?id=12347, where it seemed like PG and everyone else didn't like '+ for concatenation :)

I was thinking of the same thread. ^_^ We must have kept score in different ways, depending on which arguments convinced us and so on.

The thread on matrices and vectors was a separate issue.

I was using 'v+ as an example of an extensible, left-associative varargs operator, just to give an idea of how to implement '+ and '* so they're extensible.

I think we're all ok overloading + for adding different kinds of mathematical entities, and so on. I'm not sure what sort of monoid allows + for list concatenation.

Well, lists are mathematical entities. If that isn't intuitive, consider how we reason about basic properties of rational numbers. It's usually easiest to break them apart into numerators and denominators and form conclusions about those as integers. But if we can reason about something in terms of two pieces it has, it makes sense for our reasoning system to support arbitrary pairs rather than just the special case of rational numbers. Those pairs can be built up into linked lists.

If your concern is more about monoids, here's the Wikipedia version: "In abstract algebra, a branch of mathematics, a monoid is an algebraic structure with a single associative binary operation and an identity element."

Concatenation is associative (a . b . c is the same regardless of whether you do a . b or b . c first) and has an identity element (a . nil and nil . a are both just a), so the set of lists (or strings, etc.) together with the concatenation operator makes a monoid. Also, real numbers are a monoid under addition, since it too is associative and has an identity element (zero).

A few accumulation and repetition utilities here and there, like '++, 'mappend, and 'summing, can be generalized to all monoids... but really, the main benefit in my book is having a consistent excuse to use '+ for concatenation. :-p

-----

2 points by rocketnia 5432 days ago | link

Oh, I keep forgetting: I'm not sure I like having '+ be implemented using just a two-argument version. Naively concatenating two things at a time is embarrassingly inefficient (http://en.wikipedia.org/wiki/Schlemiel_the_Painter%27s_algor...). So, I'm sorry I suggested it!

Instead of that approach, I've been thinking about having one function that wraps the first argument in some kind of output stream, one function that submits a new argument to that stream, and one function that gets the value accumulated in that stream. So here's yet another untested, throwaway code example illustrating the functionality I'm talking about (but not the extensibility, unless you use 'extend):

  (= orig-inside inside orig-+ +)
  
  (def +streamer (x)
    (case type.x string  (w/outstring str (disp x str) str)
                 int     x
                 num     x
      (if alist.x
        (annotate 'basic-streamer
          (obj test alist func [apply join _] args list.x))
        (err "unrecognized case"))))
  
  (def fn-add-to (segment streamer)
    (if (and (isa streamer 'output) (isa segment 'string))
      (do (disp segment streamer)
          streamer)
        (and (in type.streamer 'int 'num) (in type.segment 'int 'num))
      (orig-+ streamer segment)
        (and (isa streamer 'basic-streamer) rep.streamer!test.segment)
      (do (push segment rep.streamer!args)
          streamer)
      (err "unrecognized case")))
  
  (mac add-to (segment place)
    (w/uniq g-streamer
      `(zap (fn (,g-streamer)
              (fn-add-to ,segment ,g-streamer))
            ,place)))
  
  (def inside (x)
    (case type.x output          orig-inside.x
                 basic-streamer  (rep.x!func rep.x!args)
                 int             x
                 num             x
      (err "unrecognized case")))
  
  (def + args
    (iflet (first . rest) args
      (let s +streamer.first
        (each arg rest
          (add-to arg s))
        inside.s)
      0))
Inefficient concatenation is probably what's bogging down Penknife's parser, so I'll put this technique to the test sooner or later to see how much it helps.

-----

1 point by rocketnia 5432 days ago | link

In order to get an apostrophe in that URL (http://en.wikipedia.org/wiki/Schlemiel_the_Painter%27s_algor...), I had to escape it as %27. Even when I went to edit my post, the quote had been stripped out of the recreated input. Bug?

-----

1 point by akkartik 5432 days ago | link

You can probably do a quick performance comparison and count conses like waterhouse does in that + thread. I'd be curious to see your results.

-----

1 point by rocketnia 5432 days ago | link

I haven't found my way into cons-counting.... I know it's probably sloppy, but I allocate as though it takes constant time. Given that assumption, I'm less concerned with an overall constant-time slowdown and more concerned with allowing a '+ of N sequences of length L to take O(NL) time rather than O(N^2 L) time.

This is also a mostly[1] less limited design from an interface point of view. It's straightforward to port an extension from my other design to this one: The '+streamer and 'inside functions will be extended with (fn (x) x), and 'fn-add-to will be extended with the desired two-argument behavior. (I'd personally make a macro for this, and I'd use it to (re)implement the number-and-number case.)

[1] The exception is if you want to use '+ on streamer types themselves, since a '+streamer behavior of (fn (x) x) will cause that type to be confused with whatever other type wraps itself up as that streamer type.

-----

2 points by akkartik 5432 days ago | link

"I was thinking of the same thread. ^_^ We must have kept score in different ways, depending on which arguments convinced us and so on."

Ack, you're right. So it's just me and waterhouse against +?

I tried replacing + on anarki and got immediate pushback. join does seem a long name, especially inside prn's to generate html, but what other one-character name can we use besides +? I'm leaning back towards + again, perhaps mirroring the experience of people who've tried this before.

-----

1 point by evanrmurphy 5432 days ago | link

> So it's just me and waterhouse against +?

IIRC, pg found himself against it too, but rtm was for it.

> what other one-character name can we use besides +?

One feature I like in PHP is the use of the dot (.) for concatenation. We've already loaded up that character quite a bit here in Arc, with its use in conses, rest parameters and for the ssyntax `a.b` => `(a b)`. But concatentation is at least vaguely isomorphic to consing. I wonder...

Probably not. `+` is your best bet, IMHO.

-----

1 point by akkartik 5432 days ago | link

I should mention, just for completeness, that haskell uses ++.

-----

1 point by evanrmurphy 5432 days ago | link

Didn't know that. Could you give a quick example?

-----

2 points by akkartik 5432 days ago | link

In haskell you can designate any sequence of characters as an infix operator. Here's the definition of ++ from the prelude (http://www.haskell.org/onlinereport/standard-prelude.html):

  (++) :: [a] -> [a] -> [a]
  []     ++ ys = ys
  (x:xs) ++ ys = x : (xs ++ ys)
so [1, 2, 3] ++ [4, 5] = [1, 2, 3, 4, 5]

-----

1 point by akkartik 5433 days ago | link

:) I'm convinced.

"if we can reason about something in terms of two pieces it has, it makes sense for our reasoning system to support arbitrary pairs rather than just the special case of rational numbers. Those pairs can be built up into linked lists."

Perhaps lists are continued fractions? :)

-----


Nice, I like the dot notation.

But why does it have to depart from arc's syntax? What's wrong with:

  (chain  ($ "body") (addClass "wow") (show "slow"))
Also, does = inside a def creates a local variable?

-----

1 point by evanrmurphy 5441 days ago | link

> But why does it have to depart from arc's syntax?

The problem arises when you try to make use of Arc's dot ssyntax. Here are a couple jQuery examples using the old ArcScript, which is more ssyntactically faithful to Arc:

  ($.document.`ready (fn ()              $(document).ready(function() {
    ($!a.`click (fn ()                     $("a").click(function() {
      (alert "Hello world!")))))             alert("Hello world!");
                                           });
                                         });


  ($.document.`ready (fn ()              $(document).ready(function() {
    ((($ "#orderedlist")                   $("#orderedlist")
        `addClass) "red")))                  .addClass("red");
                                         });
While you can tell that the left side is supposed to be jQuery, it's completely non-obvious how the syntax transformation works. It takes too much effort every time you see the dot to remember that it has nothing to do with JavaScript's dot operator, but rather means:

  $.document => ($ document) => $(document)
You could just resolve not to use Arc's dot ssyntax because of the confusion with JavaScript's dot operator, but now you have this great unused character you can decide what to do with. Since this is a DSL for JavaScript, and JS's dot operator plays such a key role in idiomatic JavaScript, why not let Arc's dot compile to it?

> Also, does = inside a def creates a local variable?

This depends on whether you want a DSL that's really close to the metal of JavaScript, or one that makes some nice abstractions for you, like always guaranteeing lexical scope or making all statements valid expressions. I can see the utility of each. Maybe they could both be available as different modes you can toggle:

  ; Metal mode

  (def bar ()                     
    (var= pet "dog")                       
    (return                                 
      (alert                                ; Both compile to this JavaScript        
        (+ "my pet is a " pet)))            
                                            var bar = function() {
  ; Abstraction mode                          var pet = "dog";
                                              return alert("my pet is a " + pet);
  (def bar ()                               };
    (= pet "dog")                 
    (alert                        
      (+ "my pet is a " pet)))

-----

1 point by rocketnia 5441 days ago | link

With "var bar = function() { ... };", I bet you run into self-recursion issues. Unless I'm mistaken, the variable "bar" isn't in the scope of the closure.

At the very least, (def bar ...) and (def foo ...) won't be corecursive, since foo isn't in bar's scope. That in particular is what the "function bar() { ... }" syntax is for. ^_^ I believe that kind of statement assigns to a variable that's implicitly declared at the top of the current scope, meaning it's visible to itself and all the other functions declared that way, without the need for a "var bar, foo; bar = ...; foo = ...;" idiom.

makes some nice abstractions for you, like always guaranteeing lexical scope

For me, that's one of those "why would I ever want the language to do this for me" things. ^_- Maybe this means I'm a Blub programmer, but I prefer explicitly declaring the new scope's variables rather than explicitly declaring which variables I take from the outer scope.

Declaring only globals is the worst, 'cause it implies not being able to shadow a local with another local of the same name. Ever since 'it, '_, and 'self, I've been accustomed to using the same variable names over and over, even in nesting scopes. Shame on me, but still. :-p

You can probably disregard most of this rant, 'cause it's probably just as annoying either way. XD Maybe someone else has an opinion though.

-----

1 point by evanrmurphy 5441 days ago | link

> With "var bar = function() { ... };", I bet you run into self-recursion issues. Unless I'm mistaken, the variable "bar" isn't in the scope of the closure.

You're right, thanks for the correction. There is an alternative to the "function bar() { ... }" syntax that allows for recursion, though:

  var bar;
  bar = function() {
    ...
  };
This is how CoffeeScript compiles function definitions, and it's what I meant to write. I'm not really sure yet about all the advantages and disadvantages when compared to the other format.

> You can probably disregard most of this rant, 'cause it's probably just as annoying either way.

Your rants are always welcome here! ^_^

---

Update: Sorry, rocketnia. Reading your comment more carefully I see you already talked about this:

> I believe that kind of statement assigns to a variable that's implicitly declared at the top of the current scope, meaning it's visible to itself and all the other functions declared that way, without the need for a "var bar, foo; bar = ...; foo = ...;" idiom.

I guess whether you use the explicit var declaration is largely a matter of taste. Declaring all variables explicitly at the top of the scope more transparent, but less concise. I suppose since CoffeeScript tends to declare other variables at the top of the scope, including functions variables in that group allows for a certain consistency.

-----

1 point by evanrmurphy 5431 days ago | link

> With "var bar = function() { ... };", I bet you run into self-recursion issues. Unless I'm mistaken, the variable "bar" isn't in the scope of the closure. At the very least, (def bar ...) and (def foo ...) won't be corecursive, since foo isn't in bar's scope.

Actually, both of these cases are working fine for me with the ``var bar = function() {...};`` format. This is testing in Chrome 5 on Linux:

  // recursion

  var foo = function(x) { 
    if (x == 0) 
      return "done"; 
    else 
      return foo(x - 1); 
  };

  foo(5)
  // => "done" 

  // corecursion

  var foo = function(x) { 
    if (x == 0) 
      return "done"; 
    else 
      return bar(x - 1); 
  };

  var bar = function(x) { 
    if (x == 0) 
      return "done"; 
    else 
      return foo(x - 1); 
  };

  foo(5)
  // => "done"
  bar(5)
  // => "done"
Am I missing something here?

-----

1 point by rocketnia 5430 days ago | link

Actually, you're right. ^^; In JavaScript, all variables declared in a function are visible throughout the function, even before the declaration statement.

  var foo = 4;
  
  (function(){
    alert( foo );  // "undefined"
    var foo = 2;
  })();
My mistake. ^_^;

-----

1 point by evanrmurphy 5441 days ago | link

> why would I ever want the language to do this for me

Hmm... so would you also prefer that return statements never be implicit?

I'd like to design the bottom layer operators of this compiler to have a one-to-one correspondence with JavaScript and zero automagic. This means explicit var declarations, explicit returns and an embrace of the expression/statement dichotomy. Then, we should be able to layer on top all desired forms of automagic using macros so that programmers can opt-in or opt-out as they like.

Or is it really naive to think it could work out this way? :P

-----

2 points by rocketnia 5441 days ago | link

Nah, that comment was only about function scope. I'm saying I really prefer the kind of scoping Arc and JavaScript both have, where variables shadow each other and closed-over variables don't need to be declared (but local ones do). I can't put my finger on why... but wait, pg can. :D

http://www.paulgraham.com/arclessons.html

Skip to "Implicit local variables conflict with macros."

As for implicit return statements, I don't know what you expect me to be, but I'm for them. ^_^ I think you can always say "return undefined;" in JavaScript to accomplish the same thing as leaving out the return statement, so in a way, leaving the return statement out is just syntactic sugar.

I'm for explicit return statements too, for the purposes of short-circuiting. However, they probably won't play well with macros, for the same reason as pg encountered with implicit local variables: Lexical scope boundaries might pop up in unexpected places and change the meaning of "return;".

> Or is it really naive to think it could work out this way? :P

Well, I do have lots of suggestions. They go pretty deep into the fundamental design too. That said, they sort of lead to not programming in Arc. You'll see what I mean in a second (unless I go scatterbrained on ya again).

I think it would be best to treat this as a combinator library. You can have utilities that build not only JavaScript expressions and statements,* but also other intermediate forms, like lists of statements. I wouldn't be surprised to see http://en.wikipedia.org/wiki/Control_flow_graph pop up at some point. As the combinator library goes on, it can introduce more convenient construction syntaxes, including full DSLs.

If this is beginning to sound similar to the topic I just posted, "A Language Should Target Moving Platforms"[1], that's because it is. We're talking about generating JavaScript code. It's very closely related to the topic of generating code in general. :-p

I was developing Penknife not too long ago. It's a language much like Arc, but what's important here is that its expressions are compiled and macro-expanded to an intermediate format first--i.e. parsed into an AST--and it's easy to make closures whose ASTs can be inspected. My plan is for the Penknife AST itself to be extensible using user-defined types, and my goal is for non-Penknife ASTs to be made using the same syntax technology used elsewhere in Penknife, right down to using the same compiler.

So yeah, this is my apprach to a JavaScript DSL. ^_^ I don't expect you to go adopt the whole approach, but if you do want to pursue it, or if there's some aspect of it that would fit into your design too, be my guest.

* Fine-grained control is fine by me. :-p So is automagic. I want it all!

[1] http://arclanguage.org/item?id=12939

-----

1 point by akkartik 5441 days ago | link

"Lexical scope boundaries might pop up in unexpected places and change the meaning of "return;"."

I couldn't believe this, but I looked around and yes, it's true, "..the scope container in javascript is a function." (http://mark-story.com/posts/view/picking-up-javascript-closu...)

That seems bad. Don't create a new function in js everytime I do a let in arc. But I don't know if you can avoid doing so.

Perhaps one way to do it would be to play a little fast and loose with semantics. Assume let translates to the scope of the containing function.

-----

1 point by evanrmurphy 5440 days ago | link

> I couldn't believe this, but I looked around and yes, it's true, "..the scope container in javascript is a function."

Yep! Function scope + no tail-call optimization = a formidable challenge. ^_^

> Perhaps one way to do it would be to play a little fast and loose with semantics. Assume let translates to the scope of the containing function.

Interesting approach. I'll play around with this. There might also be weird hacks that could simulate let without costing a whole function on the stack. For example, you might be able to get away with something like this:

  (let x 5         var _temp = true;
    ... )          while (_temp) {
                     _temp = false;
                     var x = 5;
                     ...
                   }
Or this:

  (let x 5         var _temp = true;
    ... )          for (var x = 5; _temp = false; _temp)                           
                     ...
                   }
But I'm not sure yet if these work, or if they're less expensive than the straightforward let translation:

  (let x 5         (function() {
    ... )            var x = 5;
                     ...
                   }).call(this);

-----

2 points by rocketnia 5440 days ago | link

I tried these out, and they don't seem to help, at least in Chrome:

  var x = 1;
  {
      var x = 2;
      var y = "but why!?";
      alert( x );           // 2
  }
  alert( x );               // 2
  alert( y );               // but why!?
  
  var x = 1;
  do
  {
      var x = 2;
      var y = "but why!?";
      alert( x );           // 2
  }
  while( false );
  alert( x );               // 2
  alert( y );               // but why!?
The next thing you might try is mangling variable names. If you expect the JavaScript code to never use threads (which it doesn't, I think), you could temporarily replace the variable values using assignment.

For a high-level (and non-idiomatic) approach, it's possible to implement trampolining using exceptions. I've done it in about a page of JavaScript (right next to my Fermat's Last Theorem proof, of course of course :-p ), and I think the API can be as simple as this:

  - Use bounce( foo, foo.bar, 1, 2 ,3 ) instead of
      foo.bar( 1, 2, 3 ) in tail positions. This throws an
      exception containing information about the next call
      to make.
  - To begin a trampolining loop, call the function with
      trampoline( foo, foo.bar, 1, 2, 3 ). This should be
      done when calling functions at the top level and when
      calling functions from within functions that are to be
      exposed to other JavaScript utilities and DOM event
      handlers (which won't call trampoline themselves).
Okay, so determining when a closure could "escape" into the outside world might be hard. O_o To avoid that trouble, uh, keep a global variable saying that a trampolining loop is already in progress, and use trampoline( ... ) for almost every single call? Well, that'll complicate things a little. ^_^;

-----

1 point by evanrmurphy 5440 days ago | link

> The next thing you might try is mangling variable names.

I think this could work pretty nicely, actually:

  (let x 5         var _x;
    ... )          _x = 5;
                   ...
Here's an example with a global and local variable:

  (= x 6)            var x, _x;
  (let x 5           x = 6;
    (alert x))       
  (alert x)          _x = 5;
                     alert(_x); // => 5

                     alert(x); // => 6
If you use multiple lets with the same variable name in one scope, you would need to differentiate with additional underscores or something:

  (let x 5           var _x, __x;
    (alert x))         
  (let x 6           _x = 5;
    (alert x))       alert(_x); // => 5

                     __x = 6;
                     alert(__x); // => 6
Of course, there is still some risk of variable name collision, and the output isn't super readable. Maybe it's good to have two operators, `let` and `let!`. The former uses function scope, so it's safer and compiles to more readable JavaScript, but it increases the stack size. The latter (let!) is the optimized version that uses one of the schemes we've been talking about.

-----

1 point by rocketnia 5440 days ago | link

What's your policy regarding the ability for people to say (let _x 5 ...) to spoof the mangler? ;) Also, will the name "let!" conflict with ssyntax?

These questions shouldn't keep anyone up nights, but I thought I'd point them out.

-----

1 point by evanrmurphy 5440 days ago | link

> What's your policy regarding the ability for people to say (let _x 5 ...) to spoof the mangler? ;)

The underscore prefix system would be nice because it's pretty readable, but it's not necessary. In the worst case, we can use gensyms. I thought using underscores for internal variables might work because CoffeeScript does it [1], but maybe it won't. So you raise a good point. :)

> Also, will the name "let!" conflict with ssyntax?

Yes. I'm starting from scratch with the ssyntax. `!` is a regular character now. It can be used in names like `let!`, and when by itself in functional position it compiles to the JavaScript negation operator:

  (! x)   =>   !x
`.`, `:`, `~`, `[]`, `&` and friends are out, too. This may displease my Arc buddies, but I just found it too difficult to think in JavaScript when the gap in meaning between Arc and JS for these characters was so large. `.` compiles to the JS dot operator now, `{}` to the JS object literal and `[]` to the array literal (see [2] for some details). Note that quote ('), quasiquote (`), unquote (,), unquote-splicing (,@) and string quoting (") should all work as expected.

---

[1] http://jashkenas.github.com/coffee-script/#comprehensions

[2] http://arclanguage.org/item?id=12948

-----

1 point by akkartik 5441 days ago | link

Probably just different aesthetics. I sense that you're thinking in javascript and rocketnia is thinking in arc.

-----


I did try to give it a shot, but I'm not quite at the place where I can hack that much lisp.

-----

2 points by fallintothis 5446 days ago | link

If by "atom" you mean non-conses that aren't tables, strings, or functions:

  $ diff -u old-ac.scm new-ac.scm
  --- old-ac.scm  2010-12-01 10:18:36.961984734 -0800
  +++ new-ac.scm  2010-12-01 10:27:02.058606152 -0800
  @@ -647,7 +647,7 @@
   ;       ((or (number? fn) (symbol? fn)) fn)
   ; another possibility: constant in functional pos means it gets 
   ; passed to the first arg, i.e. ('kids item) means (item 'kids).
  -        (#t (err "Function call on inappropriate object" fn args))))
  +        (#t (ac-niltree (apply list fn (ar-nil-terminate args))))))
   
   (xdef apply (lambda (fn . args)
                  (ar-apply fn (ar-apply-args args))))

  arc> (1 2 3)
  (1 2 3)
  arc> (+ (1 2 3) (4 5 6))
  (1 2 3 4 5 6)
  arc> ('a 'b 'c)
  (a b c)
  arc> (map [1] ('a 'b 'c))
  ((1) (1) (1))
You could, of course, have another conditional (e.g., only do it for numbers instead of numbers, exceptions, quoted symbols, sockets, etc.). The catch-all seems dangerous. The special-casing hardly seems worthwhile. It saves you, what, a quote character for list literals (or otherwise a call to list, which isn't that much to type and makes the code clear that there's a literal list happening there)? Versus the other ideas kicking around in the comments of ac.scm, as seen in the diff.

-----

2 points by hasenj 5446 days ago | link

> a call to list, which isn't that much to type and makes the code clear that there's a literal list happening there

So far most macro-intensive lisp code isn't really clear until you know what the macro is doing, you could see (function arg1 arg2 arg3) inside an expression and you'd think it's a function call, but it could be passed to a macro and that macro would transform it into something else.

The advantage I'm hoping for is simplifying building lists (or trees) without resorting to macros.

Here's an explicit alist

  ((a b) (b c))
which otherwise would be a bit more cumbersome:

  (list (list a b) (list b c))
And this is not the same thing as

  '((a b) (b c))
tryarc:

  arc> (= a "w1" b "w2" c "w3")­
  "w3"
  arc> '((a b) (b c))
  ((a b) (b c))
  arc> (list (list­ a b) (list­ b c))
  (("w1" "w2") ("w2" "w3"))
I have to admit this is all somewhat theoretical at this point. For all I know, this pattern never occurs in lisp programs.

-----

3 points by fallintothis 5445 days ago | link

I have to admit this is all somewhat theoretical at this point. For all I know, this pattern never occurs in lisp programs.

Not enough that ambiguities wouldn't still need to be resolved with list anyway. Most of the time, you're not building up a literal tree of elements that you know will be atoms -- you're using variables. E.g.,

  (def enq (obj q)
    (atomic
      (++ (q 2))
      (if (no (car q))
          (= (cadr q) (= (car q) (list obj))) ; call to list here
          (= (cdr (cadr q)) (list obj)        ; call to list here
             (cadr q)       (cdr (cadr q))))
      (car q)))
You couldn't change (list obj) into just (obj), since (a) Arc doesn't like lexical bindings replacing macros, so (obj) would eval to an empty hash table; (b) even if that was fixed (it's easy to do, but vanilla Arc's still bugged), what if you're enqueuing a function, list, string, or hash table? (obj) would eval either to an error (not enough arguments) or would call your function, which is surely a bug. Thus, you need list.

  (def pair (xs (o f list))
    (if (no xs)
         nil
        (no (cdr xs))
         (list (list (car xs)))      ; calls to list here
        (cons (f (car xs) (cadr xs))
              (pair (cddr xs) f))))
could only be changed to

  (def pair (xs (o f list))
    (if (no xs)
         nil
        (no (cdr xs))
         (list ((car xs)))           ; since ((car xs)) would be a list
        (cons (f (car xs) (cadr xs))
              (pair (cddr xs) f))))
which isn't really clearer, and runs into the same problems if (car xs) happens to be a string, list, function, or hash table.

Even

  (def split (seq pos)
    (list (cut seq 0 pos) (cut seq pos))) ; call to list here
couldn't be changed, since seq should be a list or string.

See also the definitions in arc.arc of insert-sorted, reinsert-sorted, defsets of car/cdr/caar/cadr/cddr, setforms, a lot of macro expansions (like obj, though that's arguably replaceable under the proposed scheme), and commonest. That was my point about list making the presence of literal lists explicit.

Places where you could safely use this scheme aren't really big wins anyways. You get to change

  (def queue () (list nil nil 0))
to

  (def queue () (nil nil 0))
or

  (let record (list (seconds) ip user)
in app.arc to

  (let record ((seconds) ip user)
It's rare you'll have to type out a big enough tree literally (i.e., without variables that will cause the problems we've seen) to make it worthwhile, and it doesn't simplify much code that currently uses list anyway.

-----

1 point by akkartik 5446 days ago | link

What about

  `((,a ,b) (,c ,d))

?

-----

1 point by hasenj 5446 days ago | link

Too cumbersome and somewhat confusing.

Actually I think subconsciously I want to get rid of these symbols when ever possible.

More importantly, can you think of a disadvantage for implicit listing of expressions?

-----

2 points by waterhouse 5446 days ago | link

  ;(pmul-deg m) returns a function that multiplies polynomials
  ; and throws out all terms with degree > m
  ;xs is a list of polynomials
  
  ((pmul-deg 5) (car xs) (cadr xs))
I do not want this to be interpreted as an assoc-list.

-----

1 point by hasenj 5446 days ago | link

Right, and in this case I wouldn't want either.

What I'm proposing is, given a list:

  (x ....)
if x doesn't evaluate to a function, hash table, macro, (or whatever else is allowed as a first element), then as a last resort, we interpret the expression as a plain list

In your example, the first expression evaluates to a function, so the normal rules would apply as usual.

-----

5 points by waterhouse 5446 days ago | link

I see. I'll mention first that I wouldn't find this feature useful myself, and would likely be irritated that certain things didn't turn out to be errors. However, here's something I see as a problem that you can probably appreciate:

(1 2 3). Arc evaluates this expression. The car is a number. Therefore, this is interpreted as the literal list (1 2 3).

((1 2 3) 1). By the above, the car of this expression evaluates to the list (1 2 3). This, applied to the argument 1, should give us the second element (the 1th element with zero-origin indexing) of the list (1 2 3), which is 2.

(((1 2 3) 1) 6). Following the above method of evaluation, the car of this expression evaluates to 2, so we get (2 6), which you say should evaluate to the literal list (2 6). This is, of course, quite different from the list (((1 2 3) 1) 6), which is probably what someone who writes literal lists according to your system would expect. I don't think it's possible for (((1 2 3) 1) 6) to be interpreted as a literal list without throwing the Arc "data structure in functional position is interpreted as a lookup" model out the window.

For that matter, I think would already be pretty weird that, given that xs is (1 2 3), (xs u) will be either an element of xs (if u happens to be a number) or a literal list (if u isn't a number).

So, either you have a "literal lists" rule that works for nesting lists most of the time--just not when one of the lists happens to be length 2 and the cadr is a number--or you have to give up the "xs is a list --> (xs n) is the nth element of xs" rule. Perhaps you could restrict it to constructing lists of constant depth; that would be a consistent, easily understood rule that wouldn't infringe on list lookups, although it still would make (xs n) be a drastically different type of object depending on what n was, and I still wouldn't like it or find it useful.

Pedagogical digression:

You say this about using quasiquote in `((,a ,b) (,c ,d)):

> Too cumbersome and somewhat confusing. Actually I think subconsciously I want to get rid of these symbols when ever possible.

Then I think you are ignorant. My intent is not to insult you; I intend this as a statement of fact, and as implied advice (that you should learn more). Look at what quasiquote allows you to do:

  `((,a ,b) (,c ,d)) ;evaluate keys and values in assoc-list
  `((,a b) (,c d))   ;evaluate keys, not values, in assoc-list
  `((a ,b) (c ,d))   ;evaluate values, not keys, in assoc-list
  `(,(a b) ,(c d))   ;construct list of two function calls
By putting in a quasiquote and adding or not adding commas in various positions, you can specify any arbitrary pattern of evaluation for the list ((a b) (c d)). And I assure you, all of these are useful at times; above, I have merely listed ones that are common enough patterns for them to have names; attempting to define the language so that the compiler will always find the correct pattern and you'll never need to use quasiquote is a bad idea. And don't even try writing more than the most basic macros without quasiquote--it's like working without rlwrap, except that can be somewhat remedied by editing a file and defining (l) to load that file.

(Again, I don't intend to insult or flame you. I do intend to flame this idea, because I think it's bad.)

Philosophical digression:

Note, by the way, that the "data structures in functional position are interpreted as lookups" rule is pretty justifiable from a Lisp point of view. You could implement data structures as functions:

  (def my-cons (a b)
    (fn (x)
      (case x
        car a
        cdr b
        (if (isa x 'int)
            (if (is x 0)
                a
                (b (- x 1)))
            (err "Can't do this.")))))
  (def my-car (x)
    x!car)
  (def my-cdr (x)
    x!cdr)

  arc> (= x (my-cons 1 (my-cons 2 (my-cons 3 nil))))
  #<procedure: my-cons>
  arc> (x 0)
  1
  arc> (my-car x)
  1
  arc> (my-car (my-cdr x))
  2
  arc> (x 1)
  2
  arc> (x 2)
  3
The only issue is printing. Here's an example implementation:

http://pastebin.com/KK1bvv85

It isn't perfect, because that 'print function will apply any function to the symbol 'type, which may cause errors. You need some access to the implementation of primitive operators to do this right. But, of course, the language designer has that power, and could quite plausibly have implemented conses and tables as functions, and given 'pr what it needs. So, it makes sense from a very-basic-Lisp point of view that "(the-list-xs 2)" could be a function call that yields the second element of the-list-xs.

I'll add that numbers and atoms are... atomic. Pure and indivisible, the building blocks that you can make everything else in Lisp with. I'm fine with compound data structures being functions, because they can be implemented with functions as featureful as you want, but I think atoms should be simple, basic pieces.

-----

2 points by rocketnia 5445 days ago | link

I was going to say something almost just like this, so kudos. ^_^ However, I cut myself off 'cause I realized that in someone's mind, 6 could primarily be a function that constructs lists that begin with 6, and only incidentally has useful behavior with '+, '-, etc. :-p To be fair, that's pretty silly, and you have other points that are good regardless.

-----

1 point by hasenj 5445 days ago | link

I agree with the first part.

but:

> `((,a b) (,c d)) ;evaluate keys, not values, in assoc-list > ((a ,b) (c ,d)) ;evaluate values, not keys, in assoc-list

or, or ..

  ((a 'b) (c 'd))
  (('a b) ('c d))
Granted, this uses quote symbols too.

My point was finding ways to lessen the need for ` and ,

If [] wasn't already used for lambdas, I could've suggested using it as a raw-list literal. [1 2 3] wouldn't be that bad.

It's possible to use other symbols, like @[1 2 3] where '@[' is a single token, or @(1 2 3).

> Philosophical digression: (...) You could implement data structures as functions

Saw that in SICP :)

-----

2 points by fallintothis 5445 days ago | link

  ((a 'b) (c 'd))
  (('a b) ('c d))
I just noticed none of your alist examples work with the atoms-imply-lists thing -- unless you ditch list-indexing, like (xs 0). That is, even if a, b, c, and d are all atoms,

  ((a b) (c d))
would not be an explicit alist, since

  (a b) == (list a b)
and

  (c d) == (list c d)
Thus,

  ((a b) (c d)) == ((list a b) (list c d))
which throws an error since (list c d) isn't a proper index (i.e., isn't a number).

Even if you could write alists that way, you'd be restricted to only those with atom cars. Personally, I can't think of the last time I needed a literal alist. If I wanted to write them that much, couldn't use quote, and couldn't bare to use list, I'd probably just do

  (def assoc-list args (pair args))

  arc> (assoc-list 'a 1 'b 2)
  ((a 1) (b 2))
and do away with complicating evaluation rules in such fragile ways.

-----

1 point by rocketnia 5445 days ago | link

If [] wasn't already used for lambdas, I could've suggested using it as a raw-list literal. [1 2 3] wouldn't be that bad.

I don't think odd parentheses like [...] are substantially more convenient than operator applications like (list ...). In fact, when editing in a bare-bones text editor, it's a slight pain to have to figure out whether )))]))]))) is the right combination of brackets. :-p

That doesn't mean it's a bad idea altogether. I think Clojure probably has the best practical use of brackets. It uses [] for literal lists just like what you're talking about, but it also generally uses [] brackets wherever there's no operator-and-body format that needs highlighting and indenting. They're used for argument lists, for instance. I haven't heard of someone setting up an editor to take advantage of that consistency, but I'd be surprised if that wasn't the reason for it. ^_^

-----

2 points by hasenj 5445 days ago | link

> ))]))])))

One of the ideas lurking in my head was a symbol to close all open parenthesis

For example, assuming [] isn't used for anything:

  (def fun (args)
    (a (b (c (d)))))
would be written as:

  (def fun (args)
    (a (b (c (d))]  
where ] would tell the interpreter to close everything. Or maybe just close the nearest open parenthesis that's at the beginning of a line.

Granted, something like (a (b (c (d] looks a bit odd, but this looks less odd:

  (a (b (c (d (e (f (g (h)))]
And you'll be able to insert stuff in the middle without having to remember to balance parenthesis at the end:

  (a (b (c (d (x y) (z (e (f (g (h)))]

-----

1 point by rocketnia 5445 days ago | link

Didn't pg talk about this use of ] in one of the early posts on Arc?

I shy away from it only 'cause it reduces the number of editors which can make sense of the brackets.

-----

1 point by akkartik 5445 days ago | link

(Which is reason against PLT's use of [], but doesn't affect arc's chosen use.)

Incidentally [] has one major advantage over (): it doesn't require pressing the shift key every single time. In my vim and emacs I've swapped the two sets of keys in lisp mode.

-----

2 points by rocketnia 5445 days ago | link

Which is reason against PLT's use of [], but doesn't affect arc's chosen use.

Hmm? I don't provide any reasons against Racket's claim that "Using square brackets in a few key places makes Racket code even more readable." In fact, I think it does aid a bit in readability, but it doesn't help when my goal is to correct sloppy brackets. XD

What I am saying is that Arc's [+ 1 _] syntax is about as convenient as (f- + 1 _) or (f-:+ 1 _). Arc also shares the ))]))) issue, a little. It would be more noticeable if more operators accepted functions as their last argument rather than their first argument.

Incidentally [] has one major advantage over (): it doesn't require pressing the shift key every single time. In my vim and emacs I've swapped the two sets of keys in lisp mode.

You mentioned this a while ago, so I've been using only [] in my languages-in-progress. ^_^ It also helps that I begrudge () and {} for looking too similar to each other. :-p The one thing I'm worried about is that ] and [ might be less distinguishable from each other than ) and ( are.

-----

1 point by akkartik 5445 days ago | link

It would be more noticeable if more operators accepted functions as their last argument rather than their first argument.

Yeah, but they don't. Lisp idiom tends to be to put the values being operated upon last, and with good reason: you want to put last the arg most likely to be a temporary. Otherwise you risk separating function calls from their args. Compare:

  (some-function
     (some-verbose-computation
       ...
       ...)
     arg2 arg3)
with:

  (some-function arg2 arg3
    (some-verbose-computation
      ...))
Since there's this major structural constraint I think any dispatch in lisp should be on the type of the last arg. (http://arclanguage.org/item?id=12646)

-----

1 point by akkartik 5446 days ago | link

Hmm, it would involve reimplementing eval inside arc. So far the arc compiler simply converts arc expressions to scheme expressions. You can't evaluate anything then.

-----

2 points by fallintothis 5446 days ago | link

it would involve reimplementing eval inside arc

Guess I should've mentioned this in my first post. People shouldn't be getting hung up on it. The diff was in this function:

  ; call a function or perform an array ref, hash ref, &c

  ; Non-fn constants in functional position are valuable real estate, so
  ; should figure out the best way to exploit it.  What could (1 foo) or 
  ; ('a foo) mean?  Maybe it should mean currying.

  ; For now the way to make the default val of a hash table be other than
  ; nil is to supply the val when doing the lookup.  Later may also let
  ; defaults be supplied as an arg to table.  To implement this, need: an 
  ; eq table within scheme mapping tables to defaults, and to adapt the 
  ; code in arc.arc that reads and writes tables to read and write their 
  ; default vals with them.  To make compatible with existing written tables, 
  ; just use an atom or 3-elt list to keep the default.

   (define (ar-apply fn args)
     (cond ((procedure? fn) 
            (apply fn args))
           ((pair? fn) 
            (list-ref fn (car args)))
           ((string? fn) 
            (string-ref fn (car args)))
           ((hash-table? fn) 
            (ar-nill (hash-table-get fn 
                                     (car args) 
                                     (if (pair? (cdr args)) (cadr args) #f))))
   ; experiment: means e.g. [1] is a constant fn
   ;       ((or (number? fn) (symbol? fn)) fn)
   ; another possibility: constant in functional pos means it gets 
   ; passed to the first arg, i.e. ('kids item) means (item 'kids).
  -        (#t (err "Function call on inappropriate object" fn args))))
  +        (#t (ac-niltree (apply list fn (ar-nil-terminate args))))))
It works the same as any other list/table/string referencing in Arc. Things that look like function calls are compiled to (ar-apply f args), generally speaking (see ac-call), so this logic happens at runtime. Thus,

  arc> (let f [+ _ 1] (f 5)) ; evals as fn call
  6
  arc> (let xs '(a b c) (xs 0)) ; evals as cons ref
  a
  arc> (let xs "abc" (xs 0)) ; evals as string ref
  #\a
  arc> (let h (obj a 1 b 2) (h 'a)) ; evals as table ref
  1
In standard Arc:

  arc> (let x 'atom (x 5)) ; defaults to #t clause
  Error: "Function call on inappropriate object atom (5)"
With the patch:

  arc> (let x 'atom (x 5)) ; defaults to #t clause
  (atom 5)

-----

1 point by akkartik 5446 days ago | link

Ah, I did notice that. This thread feels like it's been going a long time.

-----

2 points by rocketnia 5446 days ago | link

In Anarki, you can try out something similar really quickly.

  (defcall int self-and-args
    copy.self-and-args)       ; The 'copy may be unnecessary....
This should cause (1 2 3) to evaluate to a list. You can do this for types other than 'int as desired.

I actually think your idea conflicts at a design level with 'defcall. With 'defcall, there's no real guarantee that custom types will use any given default behavior, so you can only rely on the default for built-in types, and at that point you might as well just 'defcall them all one by one rather than having a default.

-----

1 point by akkartik 5446 days ago | link

; the 'copy may be unnecessary....

Just use idfn.

-----

1 point by rocketnia 5446 days ago | link

How do you mean? Under certain implementations of defcall, you might be able to get away with (= call*!int idfn), but Anarki's current implementation is a shortcut for this kind of code instead:

  (defcoerce fn int (self)
    (fn args
      (apply list self args)))  ; might be able to use 'cons here...
My confusion about 'copy and 'cons is founded on this part of arc.arc:

  ; Can return to this def once Rtm gets ac to make all rest args
  ; nil-terminated lists.
  
  ; (def list args args)
  
  (def copylist (xs)
    (if (no xs) 
        nil 
        (cons (car xs) (copylist (cdr xs)))))
  
  (def list args (copylist args))
I don't hold onto argument lists very often, but when I do, I occasionally wonder whether I should be copying them first. I think this is the first time I've actually let that concern change my code, though. What do you think?

-----

2 points by aw 5446 days ago | link

In (fn args ...), args will be a MzScheme list terminated by Mzscheme's '() instead of Arc's nil. It's usually hard to tell the difference, since the Arc runtime treats '() like nil when it can. There are a few odd corner cases where you'll notice it, for example if you use args as a key in a table, it will be a different key value than if you construct the same list using list.

-----

1 point by rocketnia 5445 days ago | link

Ah, right. Part of me's trying to think of a way to use an extensible 'iso (like akkartik's or mine) so it could be used as a table key comparator, but without the burden of maintaining a corresponding extensible hash function. (That is, the hash function would be automatic somehow.) How feasible do you think that would be? Maybe we should have the extensible hash function, but have convenience macros that extend both functions at once?

My main motivation is to remove all traces of the non-extensible Racket 'equal?.

-----

2 points by elibarzilay 5445 days ago | link

Not that it matters much in this context, but racket's equality is extensible: http://docs.racket-lang.org/reference/booleans.html#(def._((...)

-----

2 points by akkartik 5445 days ago | link

That is awesome, thanks for the tip.

Now I want to rant about Racket documentation.

You tell me equality is extensible, and now I know that, and I go look at that page and scan it from top to bottom. I do the usual things I do to read stuff online, and I've been pretty successful with my reading, my reading comprehension scores were excellent in standardized tests, I have a phd that was about nothing if not reading opaque publications, and now I'm faced with this page and I still have no idea how to tell that equality is extensible, or how to go about extending it. If you hadn't told me I'd look at that page about booleans and have no reason to expect it to bear the slightest relationship to equality for complex user-defined data structures[1]. Search for 'extensible'. Nope. Oh, 'inspectable structures.' What the sam hill are those? It has to do with prop:equal+hash, which doesn't even look like a single token to my eyes. Is it a function? How do I use it? All this goes through my mind in a flash, and all I feel is stupid.

I keep waiting for the racket reference to gradually become clear, but it's been a year now of poring over it and I don't think it'll ever happen. It's a blind spot, platform nine and three quarters, just around the corner but at ninety degrees from all three dimensions that I can see. I would never ever have noticed that equality is extensible until you told me that was so.

[1] I scan back up and it's right there in the title: "Booleans and equality." See what I mean about feeling stupid? Why should booleans and equality be grouped together? Why should equality be in the section on datatypes and not say structures?

-----

2 points by elibarzilay 5445 days ago | link

0. Note that the actual link is broken -- it's missing a ")" in the end (it's there, but it wasn't included in the url for some reason (I don't know about the local markdown language...))

1. Yes, some of these issues are known, and we're in a constant race with improving the documentation in various ways. As always, emails to the mailing list or bug reports -- or better: suggestions and possibly patches -- all of these are always welcome.

2. In this particular case, I didn't need to guess -- I knew that it was added, so I just needed to find that reference.

3. But if I were trying to find it, the first place I'd look would be the documentation for `equal?' -- and it is indeed there, at the end of the second paragraph.

4. As for how you use this property, the text that I referred to has a link to "Structure Type Properties", which describes all of that.

5. Re the organization -- booleans and equality are grouped because they're related... It also appears as the first subsection in the datatypes chapter, which makes sense there. If you have an idea how to organize it better, then see #1.

6. Yes, it would be nice to have some section that lists all of the properties that could be used for structs. The main problem with doing this is that it's an open system, so anyone can add more properties, but it won't make sense for the core to list properties from modules outside of it. This was discussed recently, and I don't think that anyone had an idea what to do about it.

-----

1 point by akkartik 5445 days ago | link

"if I were trying to find it, the first place I'd look would be the documentation for `equal?' -- and it is indeed there, at the end of the second paragraph."

Part of the problem is that I never tried finding it, because it didn't occur to me that racket would have extensible equal?

A few months ago I was flattened - absolutely flattened - to find out that PLT has optional args and keyword args. (http://arclanguage.org/item?id=12591)

I have no idea why this is. Perhaps the problem is that people expect scheme to be a small language.

-----

3 points by elibarzilay 5445 days ago | link

Well, the optionals and keyword arguments have been in for a long time now... In fact, the current thing is a second iteration after a first facility that was more CL-like...

In any case, Racket is certainly not a small language...

-----

1 point by akkartik 5445 days ago | link

Yes, as I was ranting I was feeling bad for not having contributed to improve things. I wasn't being rhetorical about feeling stupid and guilty. Lack of understanding is a barrier but no excuse. For me to say "I have a phd, but this I can't read" is akin to saying "I've learned spanish, but chinese is hard." Well, d'uh. Deal.

Perhaps PLT needs a user guide in addition to a reference, a level of redundancy with a kinda orthogonal organization (focusing on complex tasks involving multiple advanced concepts) that'll help people triangulate on understanding, or just use what works for them.

-----

3 points by elibarzilay 5445 days ago | link

Heh, excellent idea: http://docs.racket-lang.org/guide/

(See also other such documents at http://docs.racket-lang.org/getting-started/)

-----

1 point by akkartik 5444 days ago | link

I've seen that before. Why have I not paid more attention?

Here's the extremely clear guide on extensible equality: http://docs.racket-lang.org/guide/define-struct.html#%28part...

I'm going to withdraw my rant. It's something specific about my stupidity that's causing me these problems. Still investigating.

Ah, I think I see what's been happening. Since I started out with arc I've restricted myself to the mzscheme 'ghetto' and not paid as much attention to the racket language itself. My attitude was "who knows what works in mzscheme and what doesn't." This has been the root cause of my troubles, I think.

I'm going to focus on core racket now.

-----

2 points by evanrmurphy 5445 days ago | link

Thanks for the link to Guide: Racket. I've also had trouble getting into Racket's documentation, but this looks like a much more accessible starting point than Reference: Racket.

-----

1 point by rocketnia 5444 days ago | link

(Fixed link: (http://docs.racket-lang.org/reference/booleans.html#(def._((...). I fixed it by putting another pair of parentheses around it to fool the regex. :-p )

Oh, awesome. At one point I think I knew that, too, but at some point I saw 'make-custom-hash (http://docs.racket-lang.org/reference/dicts.html#(def._((lib...) and forgot about it.

Given that prop:equal+hash exists, are there any commonly used convenience macros for implementing it for a structure? It's niftiest if they support cyclic structures too, since Racket's equality API makes that possible, but I'm not picky. ^_^ I'll probably just write something myself either way, but I'm on the lookout for inspiration.

-----

1 point by akkartik 5446 days ago | link

Oh, I didn't think that much about it. I just tried this in anarki:

  (defcall int self-and-args
    idfn.self-and-args)
and it seems to work ^_^. I can't come up with a counterexample that requires copying. Can you? Otherwise let's just not copy until something breaks. Then we'll learn something.

-----

1 point by akkartik 5446 days ago | link

You know I think I totally misunderstood your original code comment. When I read it now my response to use idfn seems kinda facetious (I was seriously suggesting it, don't know what I was thinking.)

-----

1 point by rocketnia 5445 days ago | link

Well, I don't hold it against you. :-p It gave me an excuse to elaborate about my confusion too. >.>

I think I agree with you that we shouldn't worry about holding onto the argument list as long as it isn't a tangible problem. I'm interested to hear if someone knows of a problem though. ^_^ (Edit: Oh, aw posted an example.)

-----


It would be nice if one could load some definitions into a namespace

  (load "module.arc" ns)

  ; ns is a hashtable
  ; module.arc defines 'foo'
  ; we access it with ns!foo

  (ns!foo a b c)

-----

1 point by hasenj 5457 days ago | link | parent | on: Path for .arc modules

I think I found a reasonable hack for #2:

  (def import (path) (load ($.build-path ($.getenv "arc_dir") path)))
Given that you add 'export arc_dir' to arc.sh

-----

1 point by hasenj 5454 days ago | link

Maybe instead of arc_dir, we could use ARC_PATH (similar to PYTHONPATH) which would hold a list of paths separated by colons, the first thing being the directory where arc is installed.

-----

1 point by hasenj 5458 days ago | link | parent | on: Problem with blog

Try the Anarki repository instead.

http://www.arcfn.com/2008/02/git-and-anarki-arc-repository-b...

> You need to be logged in to do that.

In the "how to run news" file, there are instructions to setup admin accounts, I'm guessing it might apply to the blog app as well.

https://github.com/nex3/arc/blob/master/how-to-run-news

> open-input-file: cannot open input file: "C:/dev/urandom"

It's trying to open '/dev/urandom' which is not valid on windows. (I don't know how to fix that).

-----

More