Arc Forumnew | comments | leaders | submit | cadaver's commentslogin
5 points by cadaver 6330 days ago | link | parent | on: Infix Math

I think treef was simply enumerating the good points.

-----

2 points by cadaver 6330 days ago | link | parent | on: Infix Math

From the arc1 source:

  ; another possibility: constant in functional pos means it gets
  ; passed to the first arg, i.e. ('kids item) means (item 'kids).
If this gets added to the language, then that's really all the support you need to make even precedence analysis happen; you'll just have to put analysis into the definitions of +-*/ rather than doing it globally in the core.

-----

2 points by vrk 6330 days ago | link

Now that's an interesting idea. However, it would create some confusion if combined with the . operator:

  item!kids   ; Should really be item.'kids
  'kids.item  ; Huh? It's the same?
Granted, it would simplify some expressions, and you would get that "dot operator as in other programming languages" for free.

-----

1 point by eds 6330 days ago | link

I was under the impression cadaver was talking about infix math, because looking in the second position for the functional argument allows infixy syntax analysis.

You could combine it with the dot operator of course but I wouldn't really see a reason for it. (Why would you use 3.+.4 instead of (3 + 4)?)

And actually, the example you gave doesn't quite work (because of quote, you can't actually put a symbol in the functional position using the . operator).

  arc> (= item (table))
  #hash()
  arc> item!kids
  nil
  arc> 'kids.item
  kids.item

-----

1 point by eds 6330 days ago | link

Yeah, I was looking at that comment right as I was writing my code. It was encouraging to see pg was thinking along similar lines.

But I'm not quite clear on your point about defining precedence in the definitions of +-*/. Perhaps you could elaborate.

-----

3 points by cadaver 6330 days ago | link

Nothing more than that user-code could redefine the arithmetic functions and do exactly the kind of analysis that your infix-eval already does in the core.

infix-eval is like an implicit function that gets added before every number-in-functional-position. If, like the arc1 comment suggests, a literal-in-functional-position gets applied to its first argument (swapped positions), then every infix expression that would have been handled with infix-eval could be handled by the first infix function:

  (1 + 2 * 3)
is currently handled like:

  ([infix-eval] 1 + 2 * 3)
but could also be handled by the first +:

  (+ 1 2 * 3)
The + is passed almost exactly the same info as infix-eval, except that has to account for itself being missing from the argument list.

EDIT: This would also take care of the case where you use infix notation within a prefix expression, e.g. (+ 1 2 * 3), which infix-eval would not get the opportunity to handle. Not sure whether that's important though.

-----

2 points by cadaver 6330 days ago | link

Example, redefining +:

  (def + args
    (infix-eval
      (cons (car args)
            (cons + (cdr args)))))

-----

1 point by eds 6330 days ago | link

Ok, I see what you are saying. It happens that + is already a scheme lambda wrapper around the scheme primitive +, so I guess you'd just modify the existing function. But you would have to add wrappers for the other primitives.

It seems a little strange to me to mix infix and prefix. But I don't see any reason to disallow it.

-----

1 point by eds 6330 days ago | link

After a little work I got +-/* to handle infix evaluation themselves, allowing ar-apply to just switch the positions of the first two arguments. This allows user defined operators, but you have to be careful to make a base case for after infix to prefix conversion. For example:

  (def % args
    (if (some [isa _ 'fn] args)
        (infix-eval (cons (car args) (cons % (cdr args))))
        (apply mod args)))
In (3 % 4 + 5), % will get called twice, once to get the initial infix conversion going, and again after the expression has been converted to prefix. So if you don't have the second case, you'll just get into an infinite loop.

I'm still trying to work out what is the best way to allow user-defined precedence. In the above case, (3 % 4 + 5) will evaluate as (% 3 (+ 4 5)) not (+ (% 3 4) 5), because by default it is lowest precedence. I wrote a little set-precedence function that can be called to do the job:

  (set-precedence % 2)
or

  (set-precedence % *)
(The second version looks up the precedence of * and sets % to that precedence.)

But this method of setting precedence seems a little like a hack. And it doesn't play nicely with =. Any suggestions would be welcome.

-----

4 points by cadaver 6330 days ago | link | parent | on: Infix Math

The variables/sub-expressions are first evaluated completely, only then infix analysis will happen. It's exactly like with lists:

  (= somelist '(1 2))
  ...
  (somelist 1)
works just like

  ('(1 2) 1)
does.

As to your second argument, the +-*/ operators are passed as first-class functions, so, there shouldn't be any optimization happening. Worst thing that can happen is that the compiler doesn't understand literal numbers in functional position, or arithmetic operators applied to first-class functions, and signals an error.

-----

4 points by eds 6330 days ago | link

Assuming x, y and scale are numbers, just

  (x * y * scale)
should do the trick. As cadaver said, the arguments are evaluated completely before infix evaluation. So you can also do

  (x * y * (sqrt z))
or

  (x * Y * (sqrt (t * u + v)))

-----

2 points by bogomipz 6330 days ago | link

Ah ok, in that case this is no less than brilliant! :)

-----


I don't know how this slowdown comes about, but myself, I don't worry about speed.

The impression I got from Arc is that, currently, almost the entirety of its substance is in its idioms, or how your write programs in it, rather than its implementation.

What I don't understand is why you say it's informally-specified. Isn't its specification the source itself, and isn't that more formal than the english-language specification of Common Lisp; I thought Paul Graham said something to that effect in one of his essays.

I hope I'm not talking above myself, being a noob and all.

-----

8 points by shiro 6332 days ago | link

> Isn't its specification the source itself

The problem of source-is-the-spec approach is that it tends to overspecify things. Especially it is difficult to represent something is "unspecified" in the form of the source code.

What's the benefit of not specifying something in the specification? It leaves a room for different implementation strategies, each of which is specialized for certain situations. It allows putting off design decisions when there's not enough experience to decide which choice is better. It can warn the user that the existing choice is purely arbitrary and it may be changed when circumstances change (note: the big point is that, a concrete implementation has to choose one, even when from the specification point of view the choice doesn't matter.)

For example, Arc's current map is restart-safe for list arguments but restart-unsafe for string arguments (restart-safe means, if you evaluate (map f xs), and a continuation is captured within f, and later the continuation is restarted and the restart doesn't affect the result of the previous return value of map.) Is it a part of "spec" so that one can rely on that exact behavior? Can I rely on that the restart actually modifies the string returned from the first call? Or is it just an arbitrary implementation choice and one should assume the result of mixing map and call/cc is unspecified?

(BTW: In R5RS Scheme it was unspecified. In R6RS Scheme it is explicitly specified that map must be restart safe.)

-----

2 points by cadaver 6331 days ago | link

Amateur warning:

I think, having a prose spec that leaves things unspecified may not be particularly useful in Arc's case; unspecified behaviour in a prose spec is difficult to automatically test for.

Generally, the benefit of a prose, incomplete spec, to program writers, would be compatibility to an abundance of implementations (in either space or time). It helps program writers approach any number of implementers, without actually meeting any particular one. But to make this happen both must read and follow the spec. This is hard on the program writer in particular; he can't automatically test for a spec in the prose, he must read the spec and test his program with an abundance of implementations.

Will Arc's target audience follow a prose spec? Will anyone?

-----

2 points by soegaard 6333 days ago | link

  "What I don't understand is why you say it's informally-  
  specified. Isn't its specification the source itself, ..."
That's just a roundabout way of saying there is no spec ;-)

-----

2 points by partdavid 6332 days ago | link

I don't think that it is. It's a way of saying that the Arc language satisfies two constraints: it is suitable for writing programs, and suitable for writing specifications. For "exploratory programming" this might be the right thing.

I don't know if Arc actually satisfies that, but I can certainly see the point. Part of this would be a lack of optimization in implementation: implement something in the most straightforward manner, and it is its own specification.

-----

2 points by cadaver 6332 days ago | link

On the other hand, in a separate english-language specification you could say that a certain thing is unspecified and leave it up to the implementation to decide on a certain behaviour. I have been reading a bit in the scheme report (that's a specification right?) and there are a certain number of unspecifications in there.

-----

1 point by cooldude127 6322 days ago | link

i don't arc is intended to have more than one implementation, or at least more than one popular implementation. if this is the case, nothing is unspecified. whatever the implementation does, that is the language.

-----

0 points by soegaard 6332 days ago | link

Letting the implementation be the spec rules out bugs in the implementation. If, say, (+ 1 1) returns 3, then it isn't a bug, since that's what the spec says.

-----

6 points by pg 6332 days ago | link

It means you've specified a bad language.

-----

2 points by soegaard 6332 days ago | link

Sure. In general it isn't so easy to figure out, whether something was done on purpose.

Example: Was it intensional that (1 . + . 1) evaluated to 2?

-----

4 points by pg 6332 days ago | link

It's rather a dishonest argument to use an example like that, because it's an artifact of bootstrapping the prototype off MzScheme. A more convincing argument would be strange behavior resulting from the way something was defined in arc.arc.

-----

9 points by kens 6332 days ago | link

Is annotate a general mechanism, or specifically for defining macros? Is ellipsize supposed to limit its output to 80 characters or display at most 80 characters of the input? Is median of an even-length list supposed to return the lower of the middle two? Is cdar supposed to be missing? Is (type 3.0) supposed to be an int? Is support for complex numbers supposed to be in Arc? Is client-ip supposed to be there, or is it left over from something? Does afn stand for "anonymous fn"? What does "rfn" stand for?

These are all real questions that I've faced while trying to write documentation. Let me make it clear that these are not rhetorical questions; I'm more interested in getting actual answers than arguing about using the code as the spec.

-----

7 points by pg 6332 days ago | link

The former; the latter; yes; yes; yes (for now); yes; the former (for now); anaphoric; recursive.

-----

6 points by parenthesis 6331 days ago | link

I've matched pg's replies up with the questions, to make this discussion easier to read:

annotate is a general mechanism

ellipsize is supposed to display at most 80 characters of the input

median of an even-length list is supposed to return the lower of the middle two

cdar is supposed to be missing

(type 3.0) is supposed to be an int (for now)

support for complex numbers is supposed to be in Arc

client-ip is supposed to be there (for now)

afn stands for "anaphoric function"

rfn stands for "recursive function"

-----

2 points by drewc 6323 days ago | link

But pg's was so much more concise! ;)

-----

4 points by eds 6331 days ago | link

>> Is cdar supposed to be missing?

> yes

Wasn't the point keeping the names car and cdr so you can compose them? (I remember reading such in one of pg's essays.) Then it seems to me to take full advantage of that you need to provide those names for use.

I don't think it is unreasonable to do the following, but it is currently not provided in Arc:

arc> (cdar '((1 2 3) (4 5 6))) Error: "reference to undefined identifier: _cdar"

Maybe this is just me missing CL's four levels of composition of car and cdr.

-----

8 points by pg 6331 days ago | link

I didn't mean Arc will never have cdar. But to avoid having a language designed according to arbitrary rules rather than the actual demands of the task, I've been trying to be disciplined about not adding operators till I need them.

-----

5 points by parenthesis 6331 days ago | link

I suppose you can cdr:car .

On the one hand, it does feel like all the c....r s should be there.

On the other hand, I think cadr is pretty much the only one I ever actually use; and it is there.

-----

2 points by drcode 6331 days ago | link

I noticed this contradiction, too... :) If we're not going to use c[ad]r composability, why not just use unique, easily distinguishable names for all of these that don't compose:

  car  --> hd
  cdr  --> tl
  caar --> inner
  cddr --> skip2
  cadr --> second
...or something like that. Unique names would reduce errors.

-----

4 points by soegaard 6332 days ago | link

Never mind the example. What troubles me with the the-code-is-the-spec approach, is that for an outsider, it is impossible to tell which decisions where made deliberately and which were accidental.

Just for the record, I find it is fair game to say there is no specification, while the experimentation phase is still going on.

-----

5 points by pg 6332 days ago | link

It doesn't matter whether features are deliberate or not. It's very common in math for someone to discover something that has interesting properties they never imagined. In fact, it's probably closer to the truth to say that if a mathematical concept doesn't have properties the discoverer never imagined, it's not a very interesting one.

Lisp itself is an example of this phenomenon. JMC didn't expect to use s-expressions in the real language, but they turned out to be way more useful than he envisioned.

I'm not just splitting hairs here, or trying to defend myself. In design (or math), questions of deliberateness are not binary. I'll often decide on the design of an operator based on what looks elegant in the source, rather than to meet some spec, just as Kelly Johnson used beauty as a heuristic in designing aircraft that flew well.

-----

1 point by shiro 6331 days ago | link

It's a good argument in general sense, but I doubt it is applicable in library API.

If you're delivering a final product, users don't care if some design is deliberate or not; they care it is good or bad. If you're deriving mathematic proof, others don't care if some choice is deliberate or not; they care if it is correct, beautiful, or useful to prove other theorems. That's because changing your choice afterwards won't break existing products or proofs that relied on the previous choices.

In the case of library API, changing your choice does break existing software relying on the library. In the current Arc case it is forewarned so it's perfectly fine, but at some point (50years from now, maybe?) you have to let more people write software on it; by that moment it should be clear that what they can rely on and what they cannot.

-----

2 points by pg 6331 days ago | link

by that moment it should be clear that what they can rely on and what they cannot

The only difference if the implementation is the spec is how they know what they can rely on. If the implementation is the spec, they decide by reading the source; if it's a document writen in English, they decide by reading that.

-----

4 points by shiro 6331 days ago | link

Implementation can be, and will be, changed, inevitably. Then does the language change as well, or the languages remains the same but just implementation is improved? How can you tell the difference purely from the source?

Some Scheme implementation evaluates arguments left to right. You can see that by reading the source. In future, it may switch it right to left, maybe for better optimization. The spec in natural language, or more formal and abstract form like in Appendix A of R6RS, can explicitly say the order of evaluation is unspecified. How you tell your users that they should not rely on the evaluation order purely by the source code, given the state of most programming languages?

Ideally I like to think the source only describes the spec and the compiler and runtime figure out the details, so maybe spec-by-source and spec-by-other-notation will converge in future. Is that what you are talking?

(Please don't argue that unspecified evaluation order is bad or not; I just use that example to illustrate the point. And incidentally, since Arc is defined in terms of Scheme, the order of argument evaluation order is just unspecified as well. But it's just delegating the spec to a lower level.)

-----

1 point by soegaard 6331 days ago | link

Actually PLT Scheme guarantees left-to-right order, but that doesn't change your point.

-----

2 points by akkartik 6331 days ago | link

One point everybody else is missing: since arc explicitly makes no claims of backwards compatibility, the notion of a spec is meaningless.

If the goal of a language is to be readable there's nothing wrong in the implementation being the spec. Consider it a form of self-hosting, or of eating your own dogfood.

---

An implementation in a reasonably high-level declarative language is a more reasonable 'spec' than one in C. More features are amenable to checking just by reading rather than by execution.

When something is obviously a bug it doesn't matter if it's a bug in the spec or the implementation.

Those two categories -- obvious bugs, and questions about what is or is not in the language that should be answered by reading the code rather than executing it -- seem to cover all the objections people have raised.

-----

1 point by shiro 6331 days ago | link

At least I'm talking about the attitude of spec-by-source in general, not particularly about Arc, FYI.

Edit: I agree that more abstract, declarative language is closer to spec-by-source. If somebody says Prelude.hs is the spec of Haskell's standard library I agree. But the core language semantics is still not in Haskell itself, is it? (I'm still learning. Correct me if I'm wrong.)

-----

1 point by almkglor 6331 days ago | link

Right. And nobody needs comments in source code, ever.

-----

1 point by akkartik 6331 days ago | link

What!?

FWIW, here's how I think comments in source code can be improved, especially in exploratory programming: http://akkartik.name/codelog.html

-----

1 point by almkglor 6331 days ago | link

Oh come on. What are comments but prose descriptions of the code?

Anyway please reparse my post with <sarcastic>...</sarcastic> around it.

-----

1 point by akkartik 6331 days ago | link

I did get the sarcasm, but not how you extrapolate from my comment.

"There's nothing wrong with a code spec in this case" != "prose specs suck"

-----

1 point by almkglor 6330 days ago | link

Hmm. Must be a bug in my reader ^^

-----

2 points by oconnor0 6331 days ago | link

The problem is that as people learn the language they will build mental maps of what works and what doesn't and in the process will write code that depends on things that could legitimately be considered bugs or arbitrary side effects of the current implementation.

Whether or not this matters to you or even should matter is another concern, but this has been a spot of contention for languages like Python and OCaml whose spec is the code.

-----


This is just a random thought of a noob: You can already use pairs, strings, hashtables in functional position, could this not be exploited likewise for numbers?

From the arc source: ... ((string? fn) (string-ref fn (car args))) ...

for numbers: ((number? fn) (apply (car args) fn (cdr args)))

You wouldn't have precedence, but it would work well with prefix notation since, if you make a mistake and use infix notation in a prefix expression, e.g. (+ 1 * 2 3), an error would be signalled.

-----

4 points by eds 6331 days ago | link

I like this idea, because it doesn't involves special meaning of braces or Algol-style function calls. I just prefer

  (2 + (3 * (sqrt 4)))
to

  {2 + {3 * sqrt(4)}}
but maybe this is just me. You could even add a fancy analysis function to do precedence which would allow things like

  (2 + 3 * (sqrt 4))
Maybe I am missing something that would create difficulties for this system, but I tried the modification suggested above and it actually worked, so I am inclined to think this could be a good way to implement infix math.

-----

3 points by cadaver 6331 days ago | link

I thought about this a little bit and I suppose the simple way would be to determine precedence based on the particular functions the symbols are bound to, rather than on the symbols themselves. But wouldn't you really want symbol precedence?

-----

2 points by cadaver 6338 days ago | link | parent | on: Reference parameters

I'm new to lisp/scheme/arc so this may not be an issue in practice, but what if you want to add a new pair to the beginning of the list?

The only way to do this would be to insert a new pair after the first, then copy the car from the first to the second, then set the first car as desired.

Unfortunately this does not work if you have references to this first pair in particular, because of its car, rather than because it is the beginning of the list;

-----

1 point by cadaver 6337 days ago | link

Now that I think of it, passing parameters by reference would not be useful in this case.

Passing lists (pairs) by reference simply means creating a level of indirection to where the address of the first pair is stored. However, in a running program, there can be an arbitrary number of such locations.

I think that the problem of lists, of which the sort order is significant and which may be modified in arbitrary ways, can only be solved by, a) having a global binding, that is, a global level of indirection that everyone uses, or b) using a pair, of which the cdr remains unused, or some other reference-object as a level of indirection.

I suppose this is where arrays come in.

-----

1 point by Jekyll 6336 days ago | link

My example wasn't exactly orthodox lisp style. Normally, you should explicitly set any variables you are manipulating, so you'd be writing: (= a (f a)) rather than relying side effects to set it for you (This is especially important when calling functions like sort). It's such a common idiom that in common lisp I have a ! macro to turn (! f a . b) into (setf a (f a . b)).

-----