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

When I read this, I was surprised to learn that Arc doesn't do it waterhouse's way!

I haven't used this ssyntax combo enough to hold a nuanced view, but a:b.c => (a:b c) seems most intuitive to me.

-----


> I'm sure you're right; we just need to figure it out for ourselves.

Maybe. :) These ideas aren't totally unheard of or untested. For example, PicoLisp has auto-quote for the number case, and it's been around since the 80s! XD

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

Instead of '(1 2 3) you can just write (1 2 3). This is possible because a number never makes sense as a function name, and has to be checked at runtime anyway.

PicoLisp also takes advantage of the initial bindings idea from my #3 to some extent. In PicoLisp, transient symbols are initially bound to themselves, and ordinary symbols are initially bound to nil.

-----

2 points by akkartik 5282 days ago | link

I think I'd be more likely to feel positively toward completely unheard-of ideas[1]. For example, I mistrust dynamic scoping more because some variants of lisp have had it. I felt positively toward ssyntax and open defs from day 1 even though they were unheard of.

But all this is just gut instinct. I'll try anything once :)

[1] Though it was good to find out about the parentage of autoquoting, thanks. I wasn't aware of that.

-----

4 points by Pauan 5280 days ago | link

I dislike default dynamic scoping because it seems like a bad idea to me. The fact that other Lisps already have it seems pretty irrelevant, except insofar that it gives us a chance to try out dynamic scope and see what it's like.

In other words: I think old ideas are fine, and new ideas are fine. Ideas should stand on their own merits. The only difference between an old idea and a new idea, is that the old idea has been tested somewhat, so we can have a better idea of whether it's a good idea or not.

-----


I'm really glad you linked to that thread. I did remember it, and I meant to link there myself.

> 2) It would be extremely bad to write code that depends on unquoted symbols getting treated as quoted symbols when they happen to be unbound.

Yes. It would be a sort of inadvertent variable capture and could be the cause of some insidious bugs. Not any more insidious than the bugs you can get from an abuse of unhygienic macros or mutation though.

> (It'd probably be a good idea to make a global-assignment thing called something like "var" warn when assigning to an already-bound variable; currently this is called "safeset", and "def" and "mac" are implemented with it.)

That is a good idea! How about warning you when you assign to a variable that existing code depends on? For example:

  > (def foo () (a b c))
  #<procedure: foo>
  > (= a 5)
  Warning: Oh no, what are you doing? You're either
  being really clever or you forgot that your previous
  code depends on this variable being unbound. Why
  didn't you just use the ' operator? It's only one
  character, for god's sake.
Of course, at this point you're not really screwed. You can save yourself by either resetting the variable with (= a 'a) or redefining foo to use explicit quotation.

> If, say, you use (a b c) to mean the list '(a b c) in your code, and then you test things out at the REPL, you'd better be careful not to give "a" a value, or it'll break your code. I think the cognitive overhead of worrying about that far outweighs the cost of using the ' operator.

I think the warning we just talked about could go a long way toward eliminating this cognitive overhead. Don't worry about it, just deal with it if you get the warning. Or don't use it in the first place because...

The proposed changes are backwards-compatible with Arc 3.1, since all they attempt to do is provide sensible defaults for things that presently raise errors. I want to emphasize that the goal here is not to eliminate or replace quote and quasiquote. Rather it's to enhance these operators by giving you the ability to use them implicitly sometimes, instead of requiring that you be explicit in cases where it's the only useful meaning possible.

My new way of thinking about this is quote inference, a la type inference.

-----

2 points by waterhouse 5283 days ago | link

> How about warning when you assign to a variable that existing code depends on?

Sometimes you do that on purpose, though. E.g. you define a function that relies on a second function, and define the second function later. (rocketnia gives an example, but I'll proceed with this one anyway.)

  > (def mod-expt (a n m)
      (fast-expt-* a n 1 (fn (x y) (mod (* x y) m))))
  #<procedure: mod-expt>
  > (def fast-expt-* (a n one *)
      (xloop (a a n n tt one)
        (if (is n 0)
            tt
            even.n
            (next (* a a) (/ n 2) tt)
            (next a dec.n (* a tt)))))
  Warning: ...
Perhaps you could make "def" smart so it wouldn't set off the warning. What if you happened to give a function the same name as an unquoted symbol, though? Maybe you'd be careful not to do that. And what if you used a global variable that you planned to define later? The warning would be inappropriate. Perhaps you'd learn to ignore it. Or perhaps you could name your global variables in a certain way and have the warning thing recognize it. And tell everyone who uses Arc to name global variables the same way, or to at least come up with a naming scheme that can be mechanically understood by the warning thing--and to stick to it. Bah humbug.

> The proposed changes are backwards-compatible with Arc 3.1, since all they attempt to do is provide sensible defaults for things that presently raise errors.

It is nice when you can introduce something without breaking old things. However, I think this thing is bad: it's fragile and shallow, and I think most programmers would just not use it and resent the time it took to understand it.

Imagine if, say, whenever mathematical operations (e.g. sqrt) were called with a list argument, then, instead of throwing an error, the function was instead applied to the car of the list. (Mapping it over the list is more likely to be useful; applying it to the average is also possible.) Or if the expression (a < b) evaluated to (< a b) when "a" evaluated to a number or anything else passable to the < function. Or if, whenever you used the variable "it" inside a then-expression in a call to "if", and "it" was otherwise unbound, it bound "it" to the if-expression (as in "aif")? Or why not all of these at once, and more?

In principle, you might be able to ignore extra little "features" like this. I think it'd annoy me, though--in the case of "sqrt" et al. being applied to lists, I'd probably think about it every time I dealt with math and lists (which I do a lot). It adds one more case to deal with to mentally evaluate any mathematical function call. The best thing that could happen is that I'd never use it, or encounter it in anyone else's code, and my mind would freed of the impulse to worry. But even if it was never used in correct code, I'd still have to think about it whenever I made a mistake and had to diagnose a problem.

By the way, perhaps you are just looking for something you'd use at the REPL, instead of a new language feature. Maybe a REPL with capabilities like Clisp's:

  [1]> (+ 1 achtung)
  *** - SYSTEM::READ-EVAL-PRINT: variable ACHTUNG has no value
  The following restarts are available:
  USE-VALUE      :R1      Input a value to be used instead of ACHTUNG.
  STORE-VALUE    :R2      Input a new value for ACHTUNG.
  ABORT          :R3      Abort main loop
  Break 1 [2]> 
It'd make it easy to put in the symbol as the value of the unbound variable. You could tweak it so that would be the default option, and you'd just have to press return again or something; or you could even make it set the unbound variable to the symbol by default. This would be on your customized REPL, of course. :-P

> Not any more insidious than the bugs you can get from an abuse of unhygienic macros or mutation though.

There's an entire style of programming devoted to minimizing and isolating mutation, and languages exist which try to disallow it entirely. There has been a lot of work done about trying to implement hygienic macros. But these things are useful enough that it's difficult to get rid of them entirely (mutation more so; many languages don't have macros at all). This idea seems it would make every variable reference (in foreign code) and every global assignment (in code you write) a potential headache, and the payoff seems to me almost zero.

-----

1 point by evanrmurphy 5282 days ago | link

Hmm... it seems that each of us has certain conveniences we want and certain sacrifices we're willing to make in order to allow for the conveniences. But which things are the conveniences and which are the sacrifices is different depending on our personal preferences. Taking your example:

> Sometimes you do that on purpose, though. E.g. you define a function that relies on a second function, and define the second function later. (rocketnia gives an example, but I'll proceed with this one anyway.)

  > (def mod-expt (a n m)
      (fast-expt-* a n 1 (fn (x y) (mod (* x y) m))))
  #<procedure: mod-expt>
  > (def fast-expt-* (a n one *)
I would have always defined fast-expt-* before mod-expt and not the other way around. I think it was aw's essay on linearizing code dependencies [1] that finally persuaded me this is a good guarantee to have. When I'm reading my code, I value the confidence of knowing that everything below line n is unnecessary for getting everything above line n to work [2].

I can't remember the last time I intentionally wrote code that didn't conform to this principle. And if you're willing to write your code this way, then it eliminates the largest problems you all have identified with quote inference. But you and rocketnia seem to place value on being able reference functions before they've been defined. So while I would be willing to trade that ability for implicit quotation, it seems you would prefer the reverse.

Have I misrepresented your views here?

---

[1] http://awwx.posterous.com/how-to-future-proof-your-code

[2] Where line n is any point in your code that's not inside of a definition.

-----

3 points by waterhouse 5282 days ago | link

I do usually put definitions of dependencies first (fast-expt-* is in fact before mod-expt in my Arc file), but sometimes I shuffle my code around, and I'd be annoyed if it complained when I did that. I like having the freedom to do it, though I don't use it all that much. But mutually recursive functions are a good example too--it's impossible to have both functions come after their dependency.

> But you and rocketnia seem to place value on being able reference functions before they've been defined. So while I would be willing to trade that ability for implicit quotation, it seems you would prefer the reverse.

It is true that I'd prefer being able to permute my definitions over having implicit quotation, but that's by far not my only reason for disliking the idea. As I said, it's a fragile, shallow add-on feature that would confuse me (by giving me weird results for erroneous code) and that would make code harder for me to reason about (whenever I see a variable reference, either that refers to something that's been defined, OR it refers to a symbol! and I can establish the latter only by ensuring that it's not defined anywhere).

By the way, why do you think this feature is a good idea? I find two quotes that seem to suggest your reasoning:

1. "My sense is that something like this would rate highly in both complexity of implementation and convenience for programming." How do you get this sense? Do you write a lot of code that uses quoted symbols? Let's take a count in my big fat Arc file:

  $ grep -o "'" a | wc -l
     107
  $ egrep -o "[^' ()]" a | wc -l
   31362
  $ egrep -o "[a-zA-Z-+$*/]+" a | wc -l
    8261
Even discounting whitespace and parentheses, quotes account for about 0.3% of the characters I type. If we count symbols, about 1.25% of the symbols I use are quoted. Are you working on something that uses a bazillion quoted symbols--symbols which would have to be quoted individually (e.g. the list '(a b c d) just requires one quote)?

2. "I just think we're missing out on such valuable real estate here!" See the paragraphs in my grandparent post beginning with "Imagine if" and "In principle". Tacking things on just because you can isn't a good idea.

-----

2 points by evanrmurphy 5282 days ago | link

> But mutually recursive functions are a good example too--it's impossible to have both functions come after their dependency.

You can do it by extending one of them:

  (def foo ())
  (def bar () (foo))
  (extend foo () t (bar))
I'm not saying this is a better way to define mutually recursive functions. Just pointing out that it's possible.

> add-on feature

Not sure what you mean by "add-on". Is xs.0 list access an add-on? This is whatever that is.

> why do you think this feature is a good idea?

It could unify the "." and "!" ssyntaxes to some degree by allowing table access with h.k instead of h!k in most cases. alists that you presently have to express with '((x 1) (y 2)) could be contracted to (x.1 y.2).

I haven't worked out all the useful applications of this yet but I'm finding it interesting and think there's some potential.

> fragile, shallow [...] Tacking things on just because you can isn't a good idea.

I'm generally disappointed by your flamey response to my interest in exploring a core language possibility that could make arc programs shorter. Your ideas and even complete disagreement are very welcome, but your overall tone is insulting. Perhaps I misread you.

-----

4 points by waterhouse 5282 days ago | link

> I'm generally disappointed by your flamey response... your overall tone is insulting.

I'm sorry, my intent wasn't to insult you. (I'm glad you explained that, though.) I thought my words were clear. Let me explain:

I called the idea "fragile" because it would be easy to break code that depended on it--just by defining a new variable. You suggested a thing that would warn upon defining a previously-used-as-unquoted-symbol variable, but rocketnia and I brought up cases where the warning would be a false positive. I considered a more sophisticated warning system--one that had some kind of mechanical procedure for guessing whether an unbound variable was supposed to be an unquoted symbol or a function to be defined later--one that required the programmer to follow one naming scheme for unquoted symbols and another for functions to be defined later. My conclusion was that for this system to work, the programmer would basically have to tiptoe around it and be very careful, or else things would break. Hence, I thought the word "fragile" was appropriate, and used it.

I called it "shallow" because the maximum benefit, in the best possible case, is that you don't have to type the ' character most of the time. Contrast this with, say, learning to use lists when you're used to naming every single variable. Not only does it become easier to, say, compute the sum of the cubes of five numbers, but you can write a single function to compute the sum of the cubes of any number of numbers! And then you can make matrices--again, referencing them with a single variable--and writing a single function to deal with n x n matrices for all n! Without lists or other compound data structures, it'd seem really hard and annoying just to deal with 2 x 2 matrices, and 10 x 10 would seem impossible. There are deep and rich benefits to using compound data structures. But the only thing this unquoted symbols idea can possibly be good for is letting you omit the ' character; I therefore thought the word "shallow" was a good descriptor.

Regarding "Tacking things on just because you can isn't a good idea". Your motivation seemed like it might be, at least partially, something like this: "We can get strictly greater functionality out of Arc if we take some things that are errors and assign them a meaning in the language. Therefore, we should do it. Let's start looking for error cases we can replace with functionality!" Your comment "I just think we're missing out on such valuable real estate here!" added weight to this interpretation. And so I attacked it with a reductio ad absurdum, giving several examples of how one might "add functionality" in this way. I hoped to show that the line of reasoning "You get strictly greater functionality, therefore it can't be a bad idea" was wrong. I summed it up by saying "Tacking things on just because you can isn't a good idea."

> I'm not saying this is a better way to define mutually recursive functions. Just pointing out that it's possible.

And there are other ways to do it[0]. But it would be impossible to do it by just writing (def foo () (bar)) and (def bar () (foo)) and putting them in the right order. Hence, this idea would make such programs more complex/verbose. (Eh, perhaps you could set up a warning system and teach it to recognize mutual recursion. I think learning about this would distract the programmer somewhat, which isn't by itself a deal-breaker but is an undesirable aspect. I suspect there are more cases yet to be covered; and you'd still have to order your definitions properly--I don't think a compiler without psychic abilities could always tell what you were going to define later; and even if you were warned when you made a function with a conflicting name, it'd be annoying to have to give your function a new name, or to change the code that used that name.)

> alists that you presently have to express with '((x 1) (y 2)) could be contracted to (x.1 y.2).

Incidentally, I do find it annoying to type out a lot of such things, and I have a routine for dealing with that. Perhaps you'd find it sufficient? So whenever I want to make a big alist, I do something like this:

  (tuples 2 '(Jan 1 Feb 2 Mar 3 Apr 4 May 5 Jun 6
              Jul 7 Aug 8 Sep 9 Oct 10 Nov 11 Dec 12))
  ;instead of '((Jan 1) (Feb 2) ...)
Note that I've redefined "(tuples xs n)" as "(tuples n xs)". It is much better this way. :-} I could also use "pair", I suppose.

Oh, and, by the way, if ssyntax were implemented at the reader level--which I think it should be; I think the current situation is just a hack that will be changed eventually--you could write '((x 1) (y 2)) as '(x.1 y.2). [I see you address this in a sister post.]

And this example suggests that your intent goes beyond merely having unbound symbols evaluate to themselves. In my post I cite at the top of this thread, I addressed problems with trying to have ((x 1) (y 2)) evaluate as '((x 1) (y 2)).

> make arc programs shorter.

That is a worthy goal, one I'd forgotten about. It's good that you brought it up. I suppose the shortness of a program is kind of a good static measure, whereas objections like "It'd confuse me" are usually only temporary, and the programmer gets used to it.

But I do believe that a) using it would create either horrible risks of breaking things or annoying false-positive compiler warnings, b) therefore I'd never use it, so it wouldn't actually make my programs any shorter, and c) it would, inevitably, make debugging harder--instead of UNBOUND-VARIABLE errors I'd get diverse results, depending on precisely what happens when a symbol is put in the place of the unbound variable.

Now, (c) also applies to having xs.0 list/table/string reference work. But (a) and (b) don't. I do use it[1], and relying on it doesn't cause any problems like the fragility I've described. And the payoffs are pretty good. Many things are significantly shorter--e.g. m.i.j for reaching into a matrix, instead of (aref m i j) or, worse, (vector-ref (vector-ref m i) j).[2] The error-obfuscation objection still applies, but I think the benefits override the objection.

[0] I was thinking you could define them in the same lexical context:

  (with (foo-val nil bar-val nil)
    (= foo-val (fn () (bar-val))
       bar-val (fn () (foo-val))
       foo     foo-val
       bar     bar-val))
[1] In fact, arc3.1 doesn't even provide "hash-ref" or "string-ref" functions, so you kinda have to use (x n). "list-ref" at least could be implemented by the user in terms of car and cdr.

[2] I was going to add: "And since I don't need to specify the type of the data structure, sometimes I or my code can forget that detail. I could change matrices to be implemented as nested hash tables or vectors, and m.i.j would still be correct." However, this part could be done with a unified "ref" function that reached into all data structures.

-----

2 points by evanrmurphy 5280 days ago | link

Thanks very much for clearing all of this up. :)

My reply has been delayed because I wanted to respond to a lot of your points in detail. I haven't found time to do that yet, but I thought I should at least say this before waiting any longer.

-----

1 point by evanrmurphy 5282 days ago | link

> alists that you presently have to express with '((x 1) (y 2)) could be contracted to (x.1 y.2).

Is there a general interest in moving ssyntax functionality to the reader? I've found some past discussions about it but wanted to know the present sentiment.

If ssyntax was processed at the reader-level, it would help in this particular scenario because then '(x.1 y.2) could evaluate to ((x 1) (y 2)) instead of (x.1 y.2).

-----

3 points by aw 5282 days ago | link

Is there a general interest in moving ssyntax functionality to the reader?

In the Arc runtime project, that was my assumption behind my choosing my matching library to implement the reader in Arc. The matching library is way more powerful than what would be needed to simply replace the Racket reader as-is; the goal is that when people want to experiment with different kinds of syntaxes or with extending ssyntax to work in more cases it will be easy to do.

-----

1 point by evanrmurphy 5282 days ago | link

That's good to know, thanks. :)

-----

2 points by rocketnia 5282 days ago | link

Yeah, I like being able to reference a function before it's defined. (Macros annoy me a little for not allowing that.) For me it's a matter of the concept of "definition" being an ambient thing, where something counts as being defined if it's defined anywhere, even later on. It's like how, in a mathematical proof or prose argument, a broad claim may be reduced into a bunch of littler inferences, some handled one-by-one systematically and some left to the reader to fill in. I've read (or tried to read) a bunch of mathematical papers or books that start out building lemma after lemma and climax in a theorem, and those might even be in the majority, but sometimes I have to approach them backwards, and then I have to backtrack to figure out what their terminology means, and it's pretty frustrating.

In education, lots of the time new topics are built upon the foundations the old topics provided, but sometimes they're built upon established motivations and provide all-new foundations, like an analysis course justifying the calculus courses that came before it, or a mechanics course casting Newton's laws in a new light.

For me, the motivation comes pretty early on relative to the implementation. I could decide to put the main control flow algorighm at the top to set the stage for the rest, or I could decide to arrange things according to the order they'll be applied--or in fact I might like having them in the reverse order, the order in which they're needed to get the result from more and more convenient starting positions. That last strategy is probably closest to dependencies-come-first coding, but I don't want to be limited to it, even if I risk choosing a frustratingly haphazard strategy.

-----

1 point by evanrmurphy 5282 days ago | link

Nice summary of the different approaches. ^_^

One reason to favor the dependencies-first approach in Arc is that we have mutability.

If you're only doing single assignment and side effect-free programming, then your code doesn't have a significant order [1]. But insofar as your program is imperative and performing mutations, the order is significant.

A consequence of this is that if you want to be able to take advantage of imperative features, you're making it harder by ordering your code any other way. I say this because even if your code is purely functional right now, when you try to insert some imperative code later, the order is going to start mattering more. And it's going to start seeming tangled and confused if it doesn't build up in the order of execution (at least it does for me).

So dependencies-first programming plays especially well with imperative code. I'm also particularly interested in it at this moment because I'm working on a refined auto-quote mechanism that could be hard to take advantage of if you're not programming this way. ;)

---

[1] Except for the macros wart you alluded to.

-----

1 point by akkartik 5282 days ago | link

Yeah, I agree: I like to see the 'business end' of code up front. aw's article made some good points I'm still mulling over[1], but upgrading things seems like such a rare event compared to the day-to-day use of code. Especially if I manage to keep up my resolution[2] to never rely on any libraries :)

---

[1] http://github.com/awwx/ar now keeps tests in a separate file. Does that weaken the case for defining things before using them? Perhaps you could define your tests bottom-up but write your code top-down, or something.

I still want to try out a test harness that analyzes dependencies and runs tests bottom-up: http://arclanguage.org/item?id=12721. That way you could write your tests in any order and they'd execute in the most convenient order, with test failures at low levels not triggering noisy failures from higher-level code.

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

-----

3 points by aw 5282 days ago | link

http://github.com/awwx/ar now keeps tests in a separate file

Not by design, as it happens. I wrote some new tests for code written in Arc, and stuck them into a separate file because I hadn't gotten around to implementing a mechanism to load Arc code without running the tests.

Though I do view writing dependencies-first as a form of scaffolding. You may need or want scaffolding for safety, or because you're working on a large project, or because you're in the midst of rebuilding.

Does that mean that you always need to use scaffolding when you work on a project? Of course not. If you're getting along fine without scaffolding, then you don't need to worry about it.

Nor, just because you might need scaffolding in the future, does it mean that you have to build it right now. For example, if I had some code that I wanted to rebase to work on top of a different library, and it wasn't in dependency order, and it looked like the rebasing work might be hard, I'd probably put my code into dependency order first to make the task either. But, if I thought the rebasing was going to be easy, I might not bother. If I ran into trouble, then perhaps I'd backtrack, build my scaffolding, and try again.

-----

1 point by rocketnia 5282 days ago | link

Especially if I manage to keep up my resolution to never rely on any libraries :)

I have effectively the same resolution, but only 'cause of Not Invented Here syndrome. :-p Nah, I use plenty of libraries; they just happen to be the "libraries" that implement Arc. I use all kinds of those. :-p

---

http://github.com/awwx/ar now keeps tests in a separate file. Does that weaken the case for defining things before using them?

That file is loaded after the things it depends on, right?

---

...you could write your tests in any order and they'd execute in the most convenient order, with test failures at low levels not triggering noisy failures from higher-level code.

I'm not sure I understand. Do you mean if I define 'foo and then call 'foo in the process of defining 'bar (perhaps because 'foo is a macro), then the error message I get there will be less comprehensible than if I had run a test on 'foo before trying to define 'bar?

---

In any case, aw's post mostly struck me as a summary of something I'd already figured out but hadn't put into words: If a single program has lots of dependencies to manage, it helps to let the more independent parts of the program bubble together toward the top, and--aw didn't say this--things which bubble to the top are good candidates for skimming off into independent libraries. If you're quick enough to skim them off, the bubbling-to-the-top can happen mentally.

Lathe has been built up this way from the beginning, basically. It's just that the modules are automatically managed, and it acts as a dependency tree with more than one leaf at the "top," rather than something like yrc or Wart with a number on every file.

I'm interested in making a proper unit test system for Lathe, so we may looking for the same kinds of unit test dependency management, but I'm not sure yet about many things, like whether I want the tests to be inline or not.

Well, Lathe has an examples/ directory, which I've ended up using for unit tests. It's kind of interesting. Lathe's unit tests have become just like its modules over time, except that they print things to tell you about their status. Being a module, an example automatically loads all its dependencies, and you can load it up and play around with the things defined in it at the REPL, which is occasionally useful for debugging the example itself. But it's pretty ad-hoc right now, and I don't, for instance, write applications so that they load examples as they start up, like you might do.

-----

3 points by akkartik 5282 days ago | link

"Do you mean if I define 'foo and then call 'foo in the process of defining 'bar (perhaps because 'foo is a macro), then the error message I get there will be less comprehensible than if I had run a test on 'foo before trying to define 'bar?"

If bar depends on foo (foo can be function or macro), and some tests for foo fail, then it's mostly pointless to run the tests for bar.

---

"That file is loaded _after_ the things it depends on, right?"

Yeah well, you gotta load code before you can run the tests for it :)

My understanding of aw's point was this: if you load your code bottom-up, then you can test things incrementally as you define them, and isolate breakage faster. Defining the tests after their code is irrelevant to the argument because it's hard to imagine an alternative.

If you put your tests in a separate file and run them after all the code has been loaded, you can still order them bottom-up. So to answer my own question, no, keeping the tests in a separate file doesn't weaken aw's argument :)

-----

3 points by aw 5282 days ago | link

There is a small difference: if you've loaded only the code up to the point of the definition which is being tested when you run the test (either by writing tests in the same source code file as the definitions, or by using some clever test infrastructure), then you prove that your definitions aren't using anything defined later.

Of course you can probably tell whether code is in prerequisite order just by looking at it, so this may not add much value.

-----

1 point by aw 5282 days ago | link

whether I want the tests to be inline or not

Something I've been thinking about, though I haven't implemented anything yet, is that there's code, and then there's things related to that code such as prerequisites, documents, examples, tests, etc. The usual practice is to stick everything into the source code file: i.e., we start off with some require's or import's to list the prerequisites, doc strings inline with the function definition, and, in my case, tests following the definition because I wanted the tests to run immediately after the definition.

But perhaps it would be better to be able to have things in separate files. I could have a file of tests, and the tests for my definition of "foo" would be marked as tests for "foo".

Then, for example, if I happened to want to run my tests in strict dependency order, I could load my code up to and including my definition of foo, and then run my tests for foo.

-----

1 point by akkartik 5282 days ago | link

"the tests for my definition of foo would be marked as tests for foo."

In java or rails each class file gets a corresponding test file in a parallel directory tree. I find it too elaborate, but it does permit this sort of testing classes in isolation.

-----

3 points by rocketnia 5283 days ago | link

  > (def maximin (x) (check x number (apply max (map minimax x))))
  #<procedure: maximin>
  > (def minimax (x) (check x number (apply min (map maximin x))))
  Warning: Oh no, what are you doing?
  > O_O
The proposed changes are backwards-compatible with Arc 3.1, since all they attempt to do is provide sensible defaults for things that presently raise errors.

They're not compatible with Arc programmers who want to get those errors. Not all errors signify places where extensions can roam free. For instance, extending the 'err function itself would be particularly silly. Where and how clearly to draw the line is a matter of opinion, but I think waterhouse and I are both in the "we want errors" camp here. ^^;

-----

3 points by akkartik 5282 days ago | link

As I said before (http://arclanguage.org/item?id=13830), I'm in the camp of "this is probably a bad idea, but let's try it anyway and see where it leads." It's my new resolution: to try harder to not be conservative in trying out design choices with wart: http://arclanguage.org/item?id=13694

I still plan to do 'production' work with wart, so I'm going to try not to go off the deep end. One experiment at a time, try to falsify new changes as fast as possible, etc.

Update: This quote about hedging seems a propos: http://akkartik.name/blog/14251481

-----

1 point by rocketnia 5282 days ago | link

And I'm making Penknife so I can explore things like this in their own quarantined namespaces where they can coexist with my other experiments in the same program. ^^ I'm not sure if my strategy's any good though; I fear it won't put enough pressure on certain experiments to see what their real strengths are. It kinda banks on the idea that people other than me will use it and apply their own pressure to their own experiments.

A strategy I'm not comfortable with? Is this also hedging? :-p Nah, in this case I'm not also taking any other strategy I'd prefer to succeed. Or is that necessary? Maybe I'm hedging, but not diversified. Probably I'm just in a bad place. :-p

-----

2 points by akkartik 5269 days ago | link

"I fear it won't put enough pressure on certain experiments to see what their real strengths are. It kinda banks on the idea that people other than me will use it and apply their own pressure to their own experiments."

Even better if you could get others to put pressure on your experiments.

-----

1 point by rocketnia 5269 days ago | link

"Even better if you could get others to put pressure on your experiments."

Well, that's both sides of the issue I'm talking about. I do want people to put pressure on each other's experiments, but I expect to promote that by reducing the difficulty involved in migrating from one experiment to another. Unfortunately, I expect that'll also make it easier for people not to put any more than a certain amount of pressure on any one experiment.

Or are you talking about my experiments in particular? :)

-----

1 point by akkartik 5269 days ago | link

No, you understood me right. If you make it too easy to fragment the language the eyeballs on any subsurface get stretched mighty thin.

This might actually be one reason lisp has fragmented: it's too easy to implement and fork, and so it has forked again and again since the day it was implemented. Suddenly ruby's brittle, hacky, error-prone parser becomes an asset rather than a liability. It's too hard to change (all those cases where you can drop parens or curlies at the end of a function call), and it keeps the language from forking.

-----

2 points by evanrmurphy 5269 days ago | link

Upvoted for the new quoting style. I think double quotes + italics is a winner for news.arc forums. (I usually do a right angle bracket + italics, but I like the way yours looks better.)

-----

1 point by evanrmurphy 5283 days ago | link

  > O_O
Yeah, that particular warning system I was sketching out gives false positives. It would need to be refined.

> They're not compatible with Arc programmers who want to get those errors. Not all errors signify places where extensions can roam free.

Thanks for reminding that errors are sometimes desirable. I just think we're missing out on such valuable real estate here!

-----


You seem very excited about this feature, but I'm afraid I'm having trouble understanding what it's about. :-/

Is it a sort of base case upon which all functions' extend layers can be built up? What you've written about it so far reminds me of Ruby's method_missing. Is that a related idea?

-----

2 points by rocketnia 5283 days ago | link

You seem very excited about this feature, but I'm afraid I'm having trouble understanding what it's about. :-/

I expected that. ^_^;; I kept trying to find ways to emphasize the motive, but I kept getting swept up in minutia.

---

Is it a sort of base case upon which all functions' extend layers can be built up? What you've written about it so far reminds me of Ruby's method_missing. Is that a related idea?

I think you're on the right track. The point of my fail parameter is to explicitly describe cases where a function is open to extension. Sometimes a function call has a well-defined return value, and sometimes it has a well-defined error value. If it doesn't end in either of those ways and instead fails--by calling its fail continuation--then it's undefined there, and you're free to replace it with your own function that's well-defined in all the same cases and more.

That was the original idea, anyway. It's a little flawed: If another function ever actually catches a failure and does something with it, then its behavior relies on the callee being undefined in certain cases. Take 'testify for instance; if 'testify checks a 'testify-extensions function first but acts as [fn (x) (iso x _)] on failure, then you can't actually extend 'testify-extensions without changing the behavior of 'testify. In fact, if you replace a function with another function that calls the first one ('extend as we usually know it), then you can no longer extend the first one without impinging upon the new one. But in practice, I think it'll work out well enough.

The alternatives ways to deal with extension I see are a) encoding defined-ness in the return value using a wrapper type, and b) throwing an exception. In a language where almost everything's extensible, to have to unwrap every return value would be madness. As for exceptions, it's hard to tell whether the exception came from the function you called or some function it called. In fact, I was actually trying figure out how to have function calls take ownership of exceptions when I finally realized there needed to be a per-function-call ID, generated on the caller's side; this idea refined a little to become fail parameters.

I know, I digressed a bit again. XD Did it work for you this time?

-----

1 point by rocketnia 5283 days ago | link

Also, in my Penknife draft, I'm finding myself much more comfortable using failures rather than exceptions, letting the failures become exceptions when something's called without 'failcall. Failures form a natural tree of blame; this rulebook failed because all these functions failed, and this function failed because this other function failed....

The need to write natural-language error strings diminishes to just the base cases; the rest is pretty-printing and perhaps even a bit of SHRDLU-style "Why did you decide that?" interactive tree-drilling. ^_^

Arc's known for having rotten errors 'cause it shirks the error-checking code; with fail parameters, awesomely comprehensive error reports can be made in almost a single token.

-----


My sense is that something like this would rate highly in both complexity of implementation and convenience for programming.

-----


I'm sometimes bothered by ! as well.

There is a certain sense to the way . and ! work now though. The dot in another context is used to notate the cons pair, as in (a . b) The ssyntax a.b expands to (a b), which isn't much different from (a . b) That's sort of fuzzy logic, I'll admit.

What isn't fuzzy logic is that a!b is equivalent to a.b if you could quote the b, as in a.'b Now you can probably see that the exclamation point ! happens to look like .' if you could superimpose the two characters. (Perhaps you already knew this.)

I might actually be with you on granting . the present meaning of ! I'm not sure the form (a b) needs a ssyntax, but it's really handy to have one for (a 'b) since table references are so common. I prefer scheme's use of ! and ? at the end of function names to signifcy destructiveness and predication, respectively. (For example, set! and number?)

Update: How about the vertical bar "|" to take the place of dot? a|b could expand to (a b) and a.b to (a 'b).

-----

1 point by Pauan 5285 days ago | link

That sounds okay, but I'm not convinced we should even have a syntax for (x y). When reading other people's code that uses that syntax, I felt like it would be clearer and easier to read if they had used normal parentheses.

I do think that ":" and "!" are big wins, though (even more so if "!" is changed to ".")

-----

1 point by akkartik 5285 days ago | link

In readwarp.com I have a data structure with hash tables nested several levels deep, which I access using things like userinfo.user!stations.station!read-list.doc!title. That's much more of a mouthful without ssyntax.

I'd say I use . about as much as I use !.

-----

1 point by evanrmurphy 5284 days ago | link

Just experimenting with the alternatives using your example...

1. Original:

  userinfo.user!stations.station!read-list.doc!title
2. With '.' instead of '!' and no substitute for the original '.' :

  (.title ((.read-list ((.stations (userinfo user)) station)) doc))
3. With '|' instead of '.' and '.' instead of '!' :

  userinfo|user.stations|station.read-list|doc.title
#1 actually looks suprisingly readable to me right now. I guess I've grown more accustomed to '!' than I knew. ^_^

---

Update: What about '>' ?

  userinfo>user.stations>station.read-list>doc.title

-----

1 point by rocketnia 5284 days ago | link

I've considered > too, but I keep wanting to use < and > in names, like for XML, comparators, and very special-purpose conversions (a->b). Conversions can live without it, but I don't know about comparators and XML.

Another idea is /. Arc uses / in variable names, but I can't help but think that 'w-stdout would be just as iconic as 'w/stdout. There could be some potential. ^_^

-----

1 point by akkartik 5284 days ago | link

Depending on the font '|' extends too high and low, and seems a bit too heavy a divider.

Also, thaddeus pointed out that lisp readers use || to delineate symbols, so this may be hard to change in arc.

-----

1 point by evanrmurphy 5284 days ago | link

> userinfo.user!stations.station!read-list.doc!title

If arc had a macro for chaining "!" forms (I'll call it "!!" here), then this example could be nicely expressed without so much ssyntax:

  (!! (userinfo user) (stations station) (read-list doc) title)
Switch "!" to "." and now you've joined Clojure and LavaScript: ^_^

  (.. (userinfo user) (stations station) (read-list doc) title)

-----

2 points by akkartik 5284 days ago | link

Could we get rid of the ./! distinction by having it check if the second operand is bound?

  (. userinfo user stations station read-list doc title)

-----

3 points by evanrmurphy 5284 days ago | link

I think you're onto something!

This would mean that (h k) in table access would sometimes be synonymous with (h k). If k is bound, look up the value of k, else look up the symbol k. If k is ever bound but you actually wanted to pass in the symbol, you can still use (h 'k).

---

Since this all boils down to entities in functional position, why not take it a step further: auto-quote [1] all unbound symbols, the ones most relevant to this discussion being those passed as arguments to functional position entities, including functions, lists, tables and strings.

Ramifications:

  > (def f(x) (list 1 2 x))
  #<procedure: f>

  > (f x)
  (1 2 x)
  > f.x
  (1 2 x)
  > (= x 3)
  3
  > (f x)
  (1 2 3)
  > f.x
  (1 2 3)
  > f.x.0
  1

  > f.h
  (1 2 h)
  > (= h (obj k1 'v1 k2 'v2))  ; [2]
  #hash((k1 . v1) (k2 . v2))
  > f.h
  (1 2 #hash((k1 . v1) (k2 . v2)))
  > f.h.2
  #hash((k1 . v1) (k2 . v2))
  > f.h.2.k1
  v1
I like this...

---

[1] Perhaps another way to think about this, instead of in terms of quotation, is that all symbols are initially bound to themselves. (Note that this plays nicely with t and nil.)

[2] Actually, by the same principle this could be written:

  (= h (obj k1 v1 k2 v2))
You don't need to quote v1 and v2 because they're unbound in the enclosing environment.

-----

1 point by Pauan 5284 days ago | link

I really like the idea of combining the two. It seems like there's such a small distinction between them that they really serve a similar purpose. This would also solve all the problems with discussions about alternate syntaxes, since there would be a single syntax for both.

Crazy idea, having symbols bound to themselves by default. I wonder if that would cause any problems, though... sounds like an interesting idea to experiment with.

-----

1 point by evanrmurphy 5284 days ago | link

No responses yet, eh? I should have known that the incredible glory of this idea would leave you all speechless. XD

Here's another thing it gives you for free, super convenient alist initialization:

  > (x.1 y.2)
  ((x 1) (y 2))
  > (= a that)
  ((x 1) (y 2))
  > (alref a x)
  1
And then, with a variation on http://awwx.ws/alistcall, you get super convenient alist lookups too:

  > a.x
  1

-----

2 points by akkartik 5284 days ago | link

Patience, I was trying to make wart autoquote :) The obvious place to start is just call with one arg, but I am stumped trying to make this test pass:

  (test "call on one arg autoquotes"
    :valueof (call 'idfn xxzx)
    :should be 'xxzx)
It's the old problem with wart: there's no way to check if a variable has a lexical binding.

Hmm, I wonder if I should shoot for the moon and find the right handler to install for the UNBOUND-VARIABLE condition..

Update: I'm tantalizingly close, but perhaps it's not possible. This page outlines how: http://www.cs.cmu.edu/Groups/AI/html/cltl/clm/node322.html

But I'm concerned by the phrase, "If this [restart clause] were part of the implementation of symbol-value.." Does that mean it isn't possible?

If we can do this we can also handle 'undefined-function and turn wart into a lisp-1!

-----

1 point by evanrmurphy 5284 days ago | link

Oh I see. You're actually doing something instead of just sitting here yappin' about possibilities. Delay excused then. XD

-----

1 point by Pauan 5285 days ago | link

Fair enough, though I still think they should at least be swapped.

-----

1 point by rocketnia 5284 days ago | link

Even if Arc were supposed to be like other languages (a kinda dubious goal), a.b probably wouldn't mean field-getting, string concatenation (PHP), and function composition (Haskell) all at once. As it is, it's already closer to field-getting than either of those other things; for lists, x.9 is like getting a field named "9", while cadr.x is like getting a field named "cadr".

I actually think cadr.x is a better way to go for field-getting. If the language has good enough namespacing support, variable names are automatically more malleable than specific symbols. If anything, we could just rephrase it as x.cadr. The trouble is that refactoring x.func into (func x y) is even more work; there'd be a demand for an x.func(y) syntax. Not that that would be a bad idea, just a more complicated one. ^_^

(I originally brought up the similar idea of an a'b => (b a) ssyntax at http://arclanguage.org/item?id=13061 .)

Backing up, let's suppose Arc doesn't need to be like other languages. The current meaning of a.b fits my needs pretty well. I use (a b) significantly more often than (a 'b), partly because I tag my types using 'annotate; even if one of my types is represented by an "a!b"-friendly hash table, I access it using "rep.x!field-name", meaning I use one '.' for every '!'. (BTW, do you think ((rep x) 'field-name) would be clearer?)

Since (a b) is so common for me, I like that it uses one of the conveniently un-shifted punctuation characters (on an American keyboard):

  ;,/.'

-----

2 points by Pauan 5284 days ago | link

While I was reading your post again, I realized that "," would also work, though I'm not sure we want to use such a nice character, or save it for something else. I also like the a'b syntax, so there's definitely plenty of options to consider. It's more a matter of deciding whether things should change, and if so, in what way.

P.S. to directly answer your question, yes I think that would be clearer, though more verbose. It's a bit hard for me to pick out the individual elements, because they're all squished together due to the infix syntax.

My current view of the various styles:

  (foo rep/x.field-name)       ; good
  
  (foo rep|x.field-name)       ; undecided; looks pretty weird
  
  (foo rep'x.field-name)       ; tied for favorite

  (foo rep,x.field-name)       ; tied for favorite
  
  (foo rep.x.field-name)       ; okay, but too hard to see the "."s

  (foo rep.x!field-name)       ; don't like the !

  (foo (rep x).field-name)     ; doesn't look lispy enough

  (foo (rep.x 'field-name))    ; good: very lispy, but not too verbose

  (foo ((rep x) 'field-name))  ; too verbose
I actually really like the foo,bar syntax for (foo bar). Especially when considering that the only difference between . and ! is that ! quotes the symbol, so it makes sense that the two would look very similar, right? Also consider this:

  (something foo,bar,qux)

  (foo,bar,qux.corge)

-----

1 point by Pauan 5284 days ago | link

I think it depends on the semantics of the situation. I don't like seeing things like "car.a" or "car.cdr.car.a" because I know that car and cdr are functions, so it kinda screws with my brain.

I feel like function calls should look like function calls, but when adding syntax, they should look different from non-function calls. According to that rule:

  (= foo (list 1 2 3))
  foo.2         ; good
  car.foo       ; bad

  (= foo (obj x 1 y 2))
  foo.x         ; good
  keys.foo      ; bad

  (= foo "bar")
  foo.0         ; good
  downcase.foo  ; bad
In other words, I'm okay with the . syntax when the first argument is a non-function. But I don't like seeing/using the . syntax when the first argument is a function.

For instance, the : syntax is used for functions only. I think the . syntax should be used for non-functions only. It just helps me to cognitively understand a program with the least amount of effort, but others may disagree.

I agree that Arc shouldn't especially try to be like other languages, but since the distinction between . and ! is mostly arbitrary, choosing a syntax that is more appealing to people coming from other languages is a nice bonus. Especially given that JavaScript is quite popular (for better or worse) and has some nice functional aspects to it (closures, lambdas, etc.) which make it conceptually similar to Scheme, even if the syntax is very different.

Note: if it were possible to combine the two without causing big problems (see evanrmurphy's post), then I might be okay with using the same syntax for both forms.

-----

3 points by evanrmurphy 5283 days ago | link

> I think it depends on the semantics of the situation. I don't like seeing things like "car.a" or "car.cdr.car.a" because I know that car and cdr are functions, so it kinda screws with my brain.

> I feel like function calls should look like function calls, but when adding syntax, they should look different from non-function calls.

One of the features of a language like Arc or Scheme (i.e. lisp-1's) or JavaScript is that functions are not treated especially differently from other data types.

Forget special syntax for a moment. In Arc we can call a function with (f x), a macro with (m x), access a list with (xs 0), a string with (s 0) and a table with (h k). We even call each of these, "putting the [function|macro|list|string|table] in functional position." So, even before ssyntax is introduced, Arc has taken pains to go against your wish of making function calls and non-function calls look distinct from one another.

Overloading functional position is very powerful once we do get to ssyntax because it allows us to overload that as well. Now all we have to do is introduce one syntactic convenience, a.b => (a b), and it becomes available to all the types.

If you like the syntax when it represents list, string or table access but not when it's a function or macro call, you could simply not use the syntax on functions and macros. So you have xs.0, s.0, and h.k, and you have (f x) and (m x). Otherwise, I'd consider that the root of your grievance may be that Arc overloads functional position for all these different types to begin with.

Do you think that could be the case?

-----

2 points by Pauan 5283 days ago | link

Actually, I like that Arc overloads so many things. I suspect the primary reason I dislike using . for functions is because of my JavaScript background. After years of programming in JavaScript, it's become very ingrained into me that . means "property access"

I agree that for my code it's a simple matter of not using the syntax in the areas that I don't like it. In fact, I don't need to use any syntax at all: I could use pure S-expressions if I wanted.

I guess what it comes down to is, "I want to use . for property access in tables, or at least use something that's not !" and although it looks weird to me to use . for function calls, I'll concede that Arc is not JavaScript, so I think I can tolerate it.

Thus, combining . and ! might be the best way. Alternatively, I think these would be very nice as well:

  x,y -> (x y)
  x.y -> (x 'y)
Or:

  x'y -> (x y)
  x.y -> (x 'y)
Or:

  x,y -> (x y)
  x'y -> (x 'y)

-----

1 point by thaddeus 5284 days ago | link

I can't help but to disagree.

The | is used for wrapping a symbol having spaces i.e. |my symbol|, and by having a vertical bar there it helps me remember I am referencing a symbol, not resolving a named variable.

When choosing between | and !, I'd rather have the exclamation mark.

-----

1 point by Pauan 5284 days ago | link

Note: I was referring to swapping the semantics of . and !

I don't care as much whether it's ! or / or | or whatever, but I feel like . is more natural for property accesses (keep in mind I'm coming from a JavaScript background, so I'm very used to using . in that way)

-----

2 points by thaddeus 5283 days ago | link

> I was referring to swapping the semantics of . and !

As was I, though I could have been clearer.

> I feel like . is more natural for property accesses

Fair enough. I will suggest, even though it's not natural relative to your experience, that the right path to follow on how things should work/behave in arc would be: #1. build on the current language constructs then #2. compare features from other languages and try to incorporate the best/better ones. When it comes to the question of "is language-x's implementation better than the current arc one?" I will suggest our personal preferences are secondary to building on existing arc/lisp constructs. Otherwise the language will get pulled in too many different directions and dependent upon the month (and whomever is comprising the guiding force of arc's evolution) we could find things changing back and forth - and that would be really bad.

-----


So you've baked extend into def and mac? This is very interesting!

Should = gets the same benefits? Since wart is a lisp-2 I guess it doesn't really apply [1], but if it were a lisp-1 would you treat (= fact (fn(n) ...)) the same way?

---

[1] Or does it? I'm not yet well-versed in the ways of lisp-2-ness...

-----

1 point by akkartik 5285 days ago | link

That's a good question. My immediate reaction is to make = clear the overload table..

And yes, in a lisp-2 assigning a function is different from defining one. But you can assign to (symbol-function f) in common lisp, which would have the same effect as def.

-----

1 point by evanrmurphy 5286 days ago | link | parent | on: LavaScript takes the Arc Challenge

Yeah, I see what you mean. :) Here are a few macros to combat the jQuery cruft:

  (mac ready body
    ($ (fn () @body)))

  (mac trigger (selector event args...)
    (. ($ selector) (event @args)))

  ; same macro as from http://arclanguage.org/item?id=13776
  ; but redefined in terms of the more general trigger

  (mac draw body
    (trigger 'body html @body))

  (mac handler (selector event body...)
    (trigger selector event (fn ()
      @body)))
And the example re-written using them:

  (ready
    (draw
      (+ (<input>) (<button> "submit")))
    (handler 'button click
      (= said (trigger 'input val))
      (draw 
        (<a> href '# "click here"))
      (handler 'a click
        (draw (+ "you said: " said))))))))
Do you think this is an improvement?

---

Probably the next biggest eyesore would be all the '<' and '>' characters from the html utils. You could make convenience aliases for those (e.g. input for <input> and button for <button>), and then it would look even more fluid:

  (ready
    (draw
      (+ (input) (button "submit")))
    (handler 'button click
      (= said (trigger 'input val))
      (draw 
        (a href '# "click here"))
      (handler 'a click
        (draw (+ "you said: " said))))))))

-----

1 point by evanrmurphy 5286 days ago | link

It looks like our draw macro might as well have the + string concatentation included:

  (mac draw body
    (trigger 'body html 
      (+ @body)))
Then we get even shorter:

  (ready
    (draw (input)
          (button "submit"))
    (handler 'button click
      (= said (trigger 'input val))
      (draw (a href '#
              "click here"))
      (handler 'a click
        (draw "you said: " said)))))))

-----

1 point by akkartik 5286 days ago | link

Yeah, these are certainly looking better. The crux is whether these primitives are part of a basis set (and so don't count against the token count), and whether the rest of the framework is similarly terse (no reason it can't be).

-----

1 point by evanrmurphy 5286 days ago | link | parent | on: LavaScript takes the Arc Challenge

The example can be made less verbose with the help of a simple macro to eliminate the (.html ($ 'body) ...) repetition:

  ; don't confuse body the parameter
  ; with 'body the DOM element in
  ; this macro

  (mac draw body
    (.html ($ 'body) @body))

  ($ (fn ()
    (draw
      (+ (<input>) (<button> "submit")))
    (.click ($ 'button) (fn ()
      (= said (.val ($ 'input)))
      (draw (<a> href '#))
      (.click ($ 'a) (fn ()
        (draw
          (+ "you said: " said))))))))
Also note that this is a client-side only solution to the Arc Challenge problem. Since LavaScript itself is written in JavaScript and going to be made available through the NPM, server-side and hybrid solutions will also be possible using a Node web server.

-----

2 points by evanrmurphy 5286 days ago | link | parent | on: Macros without quasiquotation

Updated the link in LavaScript's readme. :)

-----

1 point by rocketnia 5286 days ago | link

Thanks much. XD

-----

More