...currently an s-expression that is not itself quoted but whose car is ... is not a valid function call...
Sure it's a function call:
arc> ('(a b) 0)
a
Moreover, if you consider a symbol to be a function-like value that just raises an error every time it's called, then ('foo 0) is a function call too. In fact, that's how I think of it, and I consider every value to be a "function-like value" this way.
...valuable real estate! What should we use it for?
I've found it generally unfruitful to try to get inspired for a language feature by staring at a syntax. For instance, these two syntaxes have about the same set of potential uses:
('foo bar baz)
(zz foo bar baz)
In fact, everywhere we'd type "('" for the first syntax, we'd type "(zz " for the second, so there wouldn't be much difference in the overall editing experience. We can already explore the second syntax using (mac zz ...), so I'm not worried about exploring the first one too.
For your particular suggestion, we don't even need a macro.
('foo bar baz)
(list 'foo bar baz)
In this case, instead of typing (', we type (list ', and the editing experience is just as similar.
Guess I shouldn't try to have epiphanies that late at night. Thanks for the sober reply.
---
Update: Then again...
> Moreover, if you consider a symbol to be a function-like value that just raises an error every time it's called, then ('foo 0) is a function call too. In fact, that's how I think of it, and I consider every value to be a "function-like value" this way.
Isn't this a bit of a cop-out? Imagine you were proposing the introduction of first-class functions into our language, trying to conjure the possibilities of returning functions as values. And I were to reply: "Well, you can already return functions as values. They just raise error messages." There are flaws in the analogy, but do you see my point?
Let me phrase my question better: Arc allows you to put many types of things in functional position - lists, strings, hashes, functions, macros - and it offers convenient behavior for each of these cases. But for the case of putting a quoted symbol in functional position, it just throws an error. Is there some convenient behavior we could be doing here instead?
> We can already explore the second syntax using (mac zz ...), so I'm not worried about exploring the first one too.
This is a good point. I've run into something like this in arc.js, where I want to use the form (. a b c). I could hack the reader to pieces to make this possible, or I could not break dotted lists and settle temporarily for (\. a b c), making sure I actually like using the construct before going to so much trouble.
Imagine you were proposing the introduction of first-class functions into our language, trying to conjure the possibilities of returning functions as values. And I were to reply: "Well, you can already return functions as values. They just raise error messages." There are flaws in the analogy, but do you see my point?
In that case, I'd be promoting the ability to return interesting functions. Along the way I'd probably say something like, "See, you can already return functions as values. They just raise error messages. Imagine what else they could do!"
I'm not sure that's your point, though. Maybe this is more relevant:
With the way I like to think of everything as being a function, with some just being more interesting functions than others, the term "function-like value" is pretty meaningless. If we were having a different conversation, I might say "function-like value" to mean just those values that don't throw "we couldn't think of any sensible use for calling this" errors.
---
Arc allows you to put many types of things in functional position - lists, strings, hashes, functions, macros - and it offers convenient behavior for each of these cases.
This isn't important to your point, but I think you're dividing it up the wrong way.
During compile time, there are four kinds of things in functional position: Expressions, macro names, special form names, and another category to handle metafns (essentially parameterized special forms).
The "expressions" category encompasses all run time invocations of lists, strings, tables, and closures, and since the result of the expression is only determined when we run it, not when we compile it, other kinds of values can (try to) be invoked too. By the time that happens, it's ambiguous whether the error-raising behavior belongs to the language runtime, the value itself, or something intermediate like a 'defcall framework. I blame the behavior on the value because I think it makes the language and frameworks sound like they have fewer special cases.
---
Meanwhile, your suggested syntax isn't a new category, so don't worry about me crying inconsistency here. ^_^ You just have 'quote joining the ranks of 'andf, 'complement, and 'compose as a metafn: ((quote a) b c).
Alternately, maybe you'd be content with just using (defcall sym self-and-args self-and-args) instead, so that symbols are useful as functions. (That's equivalent to a certain case of having 'quote be a metafn, though; it can just be an uninteresting metafn that acts like it isn't a metafn at all.)
---
Is there some convenient behavior we could be doing here instead?
The question hasn't changed, and my answer hasn't either: Whatever the syntax does, it'll only be a few characters more convenient than a macro.
That being said, I've thought about it a bit more deeply, and here are six criteria I've come up with for a good ('a b c) syntax, some criteria being more important than others:
-
(1) It should be a form you desperately want to stuff onto one line. Otherwise (zz a b c) will be good enough despite being two characters longer. This sort of desperation makes 'do and 'fn much more convenient names than 'begin and 'lambda.
(2) It should be a form that takes at least one symbol rather than a complicated expression, since otherwise the symbol 'a in ('a b c) is wasted. Existing forms similar to what we're looking for include (let ...), (fn ...), (assign ...), and (def ...).
(3) It should be a form you never need to combine with ssyntax. Otherwise (?a b c) will probably be better, since it allows for ?a.b, (z:?a b c), and so on. This means it probably needs to be something you always use with two or more arguments (so you don't mind losing a.b and a!b) and as usually as a standalone imperative command (so you don't mind losing a&b and a:b). Existing forms that fit this profile include (assign ...), (def ...), (zap ...), and (push ...).
(4) It should ideally have something to do with the usual meaning of 'quote, just so people don't wonder why we didn't use (`a ...), (#'a ...), or a custom read macro instead.
(5) It should ideally be better than any use we can come up with for symbols as functions. For instance, it should be better than having ('abcdefg 3) behave similarly to ("abcdefg" 3), and it should be better than Jarc's usage, which is to invoke the JVM method of that name if possible. (This is less of a criterion and more like food for thought.)
(6) We should be comfortable with the meaning of ('nil 0), whether it's consistent with list accesses like ('(a b) 0) and ('(a) 0), consistent with the new syntax, consistent with both, or consistent with neither. (This is more of a gotcha than a criterion.)
-
I think (assign ...) and (def ...) are pretty good options for satisfying both the second and third criteria. How about having ('a x y z) mean (assign a (let _ (errsafe a) (x y z)))? The code ends up looking like this:
(zap sym:string foo) ; old
('foo sym:string _) ; new
(zap [* _ 10] foo) ; old
('foo * _ 10) ; new
(def idfn (x) x) ; old
('idfn fn (x) x) ; new (but without redefinition warnings)
; old
(let _ idfn
(def idfn (x)
(prn "traced!")
_.x))
; new (but without redefinition warnings)
('idfn fn (x)
(prn "traced!")
_.x)
(= setter (table)) ; old
('setter table) ; new
(= total 0) ; old
('total do 0) ; new (but you'd still use the old version, of course)
As for the first of the criteria, it's probably just a matter of how desperate someone is to 'zap things. I use 'zap all the time, so there's a good chance I'd put it to good use, even if it does look a bit weird to me at first.
The sixth criterion is handled somewhat nicely, 'cause ('nil 0) still results in a runtime error for trying to evaluate (0), and even a usage that escapes that error, like ('nil table), stumbles upon a different runtime error for attempting to rebind 'nil. These approximate the errors that would be generated if ('nil 0) and ('nil table) were list accesses.
The rest of the criteria aren't very important. The fourth is totally betrayed here--but it doesn't matter 'cause it's just an apostrophe we're talking about--and the fifth isn't something that can't be betrayed at all unless there's clearly a better idea available.
Do you plan to include support for functions that accept arbitrary keywords? How about a version of 'apply that can pass keywords too? These are useful for functions that just want to pass most of their arguments along, as in this extreme example:
; Build a function whose behavior imitates the behavior of
; calling a nonconstant expression, such as a potentially
; hackable global variable.
(def fn-late (body)
(fn args (apply (do.body) args)))
(mac late body
`(fn-late:fn () ,@body))
(= first car)
(= rest cdr)
(= past rev:rest:rev)
(= last first:rev)
If someone redefines 'car and 'cdr to work with strings, 'first and 'rest won't reflect the change. If someone redefines 'first and 'rest, I think 'past and 'last won't reflect those changes either (but I'm not sure right now). They're not hackable. Here are two ways to make them hackable again:
(= first [car _])
(= rest [cdr _])
(= past [rev:rest:rev _])
(= last [first:rev _])
(= first late.car)
(= rest late.cdr)
(= past late.rev:late.rest:late.rev)
(= last late.first:late.rev)
I prefer the first way, but you never know. ^_^
Now suppose you want to use 'late to make a function that delegates to a function that takes keyword arguments. Will the technique 'late uses, [fn args (apply _ args)], still be aufficient? I suspect it would need to change to [fn (?. keys . rest) (apply _ :keys keys rest)] or something, and I was wondering if you already had a plan for those extra features.
By the way, macros complicate this issue. If a macro is given keywords it doesn't recognize, are they just passed to it as symbols in its rest arg, or should it be treated the same way as a function?
A pattern is starting to emerge. You're thinking about redefinition much harder than me. My approach so far has been: compiler warns of redefinition, I stop the presses and go figure out if it's a problem.
"If a macro is given keywords it doesn't recognize, are they just passed to it as symbols in its rest arg, or should it be treated the same way as a function?"
I hadn't considered that, but fwiw the current wart implementation simply strips out unrecognized keyword args and the values provided for them. Hmm, that's probably wrong.
My only point with 'late is to demonstrate a utility that needs a different implementation once keyword arguments are introduced to the language, and which may have no implementation at all if the keyword argument framework isn't comprehensive enough.
But yeah, I do think about redefinition a lot. ^^ Penknife's core library, when I get around to it, will be designed for people to be able to modify it, the way people do with arc.arc. I'm also trying to give it a module system that plays nicely with that kind of invasive coding style. It's for customizability's sake.
If someone redefines 'car and 'cdr to work with strings, 'first and 'rest won't reflect the change.. They're not hackable.
It's interesting; there seems to be a tension between future hackability and <strike>verbosity</strike> brevity. Perhaps the best way to get the best of both worlds is to go for an even more dynamic interpreter. Like forth or factor, just have all name lookups happen at runtime.
Do you mean that if you say (= foo (a b c)), then (a b c) shouldn't be evaluated until you look up foo? Well, I know I wouldn't want that all the time:
(= setter (table)) ; oops ^_^
It's interesting; there seems to be a tension between future hackability and [brevity].
I think that's only a tension between being brief and being specific. It can't be avoided; if I specifically want things to be a certain way (e.g. hackable), I'm willing to leave the beaten path to get there. The real issue for me is how far off the beaten path I can get while still being brief enough not to get fed up and make a more hospitable language. :-p
There's another kind of brevity tension. I think a language that tries to maximize brevity doesn't need to worry about having a small implementation too. After all, it's probably built in a more verbose language. ...But now that's really off-topic. XD
Yeah I'm not sure I understand all the implications. It probably wouldn't be an entirely new evaluation model. Perhaps you just have def store pre-evaluated names.
I went back to your examples, and I think first:rev is already resistant to changes to first or rev (Update: confirmed [1]). What's hard is:
(= first car)
Perhaps we should give def a specialcase so that
(def first car)
creates a synonym without hardcoding the implementation. How about this?
(mac alias(a b) `(= ,a (late ,b)))
[1] Observe:
> (= first car)
> (= last first:rev)
> (last '(a b c d))
d
> (= first cadr)
> (last '(a b c d))
c
I was going to call that '=late. ^_^ There are still other cases where a global function can end up stored somewhere it doesn't stay up-to-date with the variable binding, like storing it as a behavior in a data sructure or using (compare ...). But I don't expect to need anything more brief than 'late, actually.
It's funny, I hadn't settled on the name 'late until I posted it here, and before that point I was thinking of it as 'alias. ^_^ I wanted to avoid confusion with Racket's aliases, so I changed it at the last second.
macros?
Macros generally aren't hackable anyway, right? I don't have any ideas to change that.... Well, except these I guess. :-p
a) Change the whole language over to fexprs. This is probably the most elegant way to make things hackable, but it'll probably be inefficient without a convoluted partial evaluation infrastructure (ensuring things are free of side effects, and headaches like that ^_^ ).
b) Record which macros are expanded every time a command is evaluated, and re-run commands whenever their macros change. Running commands out of order and multiple times would be pretty confusing. (I bet there'd be ways to manage it, but I for one would forget to use them at the REPL.) It would also be easy to fall into an infinite loop by trying to redefine a macro using a command that actually uses that macro.
first:rev is already resistant to changes in first or rev
Oh, good to know. ^_^ For Penknife, I tried to make [= foo a:b] produce a custom syntax if a or b was a custom syntax, but I got stuck. I ended up with [foo ...] using the local values of a and b, I think. In the process I got a bit confused about how a:b was supposed to work in the edge cases, from a design point of view.
This experiment was reflected in two commits in the Git repo: One introducing it, and one removing it 'cause of the poorly thought-out complexity it introduced. :)
Here it is. https://github.com/rocketnia/penknife/commit/3ca7a6c216cc022... Penknife's significantly more complicated than what I've been able to talk about so far, with its own ad hoc vocabulary throughout, so good luck. ^^; Then again, that's a pretty old commit, so it's actually a simpler codebase in ways. XD
The fact that 't can't be rebound is a bit ugly. Its only purpose is to be an arbitrary non-nil value. It doesn't need to be constant just to keep people from breaking things, just like 'car and 'cdr don't need to be constants.
The one rationale I can think of is that code might assume 't is bound to the symbol 't and use the symbol instead of the current value of the variable. However, that only makes a difference when checking whether something is 't, and things should generally be compared to nil instead.
As for rebinding nil, that's a bit worse. It's supposed to be considered identical to (), even on a reader level. In particular, (a b c) and (a b c . nil) are both (a . (b . (c . ()))), so the following are equivalent:
> How would you say these Penknife utilities that focus on character streams and a read macro system [compare]? Is there a big difference?
Read macros are probably a more capable system in general, if only 'cause you can make a read macro that turns your language into Penknife. :-p In fact, Racket has Scribble, which is very similar to Penknife's syntax. (http://docs.racket-lang.org/scribble/reader.html#(part._.The...)
I don't dislike read macros. I'm just optimistic about having things like #hash(...), #rx"...", and `... be unnecessary, thanks to putting operators like hash[...], rx[...], and `[...] in the global namespace where they're treated consistently with other custom syntaxes. There's no room left for read macros in Penknife's syntax, but that's just how optimistic I am. :-p
I eventually intend for certain Penknife commands to be able to replace the reader/parser, though. That's not the same as a read macro since it spans multiple subsequent commands, but it's in a similar spirit, letting syntaxes interpret the code as a stream of characters rather than a stream of self-contained commands or expressions.
> Maybe a focus on character streams is actually homoiconic in its own right, only with a finer granularity than lisp symbols?
I don't know. I don't think so. Penknife generally treats syntax (textual, abstract, or whatnot) as a domain with its own type needs. I'm not making any conscious effort to have its syntax double as a convenient way to input common data types.
Indeed, there's no notion of an "external representation" for a Penknife value either, and I'm not sure how to approach that topic. That being said, once there's even one text-serialized form for Penknife values, it's trivial to make a Penknife syntax like "literal[...]" that deserializes its body. I don't know if that counts as homoiconic either.
> Why did you name it `tf`, and in which ways is it more bare-bones than other lambdas?
There are currently two kinds of closures in Penknife: thin-fns and hefty-fns.
A thin-fn (tf) is for when all you care to do with the value is call it. Their implementation doesn't bother doing more than it has to for that purpose; right now thin-fns are just represented by Arc 'fn values.
A hefty-fn (hf) is for when you might want to reflect on the contents of the closure, including its code and the variables it captures. I'm considering having most Penknife code use hefty-fns, just in case someone finds a use for that reflection, like rewriting a library to remove bugs or compiling certain Penknife functions to JavaScript. (The latter probably won't be an entirely faithful translation, 'cause [hf ...] itself doesn't have a good JavaScript equivalent.)
> I thought that one of the special things about lisp-family languages was that they essentially were ASTs.
They're like ASTs, but they're a little bit hackish. You typically only know an s-expression is a function call once you've determined it isn't a special form. If instead every list is a special form, then basically the car of the list tells you its type, and it's equivalent to what I'm doing. (Macro forms have no equivalent in Penknife ASTs, so I'm not comparing those.)
Still, rather than just using lists, I do expect AST nodes to have completely distinct Penknife types. This is so that extending Penknife functions for different AST nodes is exactly the same experience as extending them for custom types.
> Last question: do you have an in-progress implementation of Penknife, or have you been designing it on paper so far?
Whatever I've been talking about in the future tense is still on paper, but the present tense stuff is all here: https://github.com/rocketnia/penknife
Also, if you can, I recommend using Rainbow for your Arc implementation, since it gives a noticeable speed boost, but I occasionally run it on Arc 3.1 and Anarki too. Jarc is almost supported, but I broke Jarc compatibility in a recent change because the speed was worst on Jarc anyway.
Penknife's broken up into multiple files, but they're not Lathe modules, and I don't have an all-in-one loader file for them yet either, so you sort of have to manage a dependency hell right now:
; pk-hefty-fn.arc
...
; This is a plugin for Penknife. To use it, load it just after you
; load penknife.arc and pk-thin-fn.arc.
; pk-thin-fn.arc
...
; This is a plugin for Penknife. To use it, load it just after you
; load penknife.arc and pk-util.arc.
Altogether, I think the load order should be Lathe first of all, then penknife.arc, pk-util.arc, pk-thin-fn.arc, pk-hefty-fn.arc, then pk-qq.arc. Then run (pkload pk-replenv* "your/path/to/pk-util.pk") to load some utilities written in Penknife--the slowest part--and run (pkrepl) to get a REPL. You'll have to look at the code to see what utilities are available at the REPL, but if you type "drop." or "[drop]", that'll at least get you back to Arc.
I haven't actually tried Penknife on the latest versions of Rainbow and Lathe, or Anarki for that matter. If it's buggy right now, or if you hack on it and introduce bugs, then entering "[drop]" may itself cause errors. Fortunately, you may be able to recover to Arc anyway by entering an EOF, using whatever control sequence your terminal has for that. If even that doesn't work, you're stuck. ^^
Arc seems mostly like common lisp (nil vs (), nil vs #f, unhygienic macros, ..); the only two things it shares with scheme seem to be being a lisp-1 and dot syntax for rest args. Now &o makes the optional syntax similar to common lisp as well.
For what it's worth, I think your `?` looks much nicer than `&o`, and it elegantly fits into the language so long as we continue to have no ssyntactic use for the question mark.
Yeah, the & is just a superficial point. But I've been thinking more about &key in common lisp. I thought it was pretty redundant if you get pervasive keyword args, but there's one case it doesn't cover: if you want your rest args to take priority over the optionals.
IMO, as long as we don't have ? ssyntax, ? should be usable as a local variable name, which a special parameter list meaning defeats. Conversely, &o is already impractical as a variable name thanks to & ssyntax, so it can have a special meaning in parameter lists without introducing any warts. (I consider it a wart that in official Arc 3.1, 'o can only be used as a variable name when not destructuring.)
I think I disagree. o is certainly a wart, but ? seems unlikely to be a variable name anyway. Have you actually used ? as a variable name before?
There's nothing special about ssyntax. If we would consider using ? as ssyntax, it's fair game to use for other syntactic purposes as well.
Update: A second rationale. As waterhouse found out (http://arclanguage.org/item?id=12612) arc is extremely permissive about letting us override things, so there's no point focusing on what is legal in this arbitrary implementation.
A third rationale. Right now you want to disallow ? because it's not ssyntax. When it becomes ssyntax won't it still be unusable in arglists for the same reason & is unusable now? :) At that rate we'd never get syntax inside arglists. Which may be a plausible position, but then we should be arguing about whether arglists can benefit from syntax.
Have you actually used ? as a variable name before?
I agree with you that it's unlikely. I'm mainly just a consistency nut. :) I'd like for every symbol to be seen as either special everywhere, maximizing its potential power, or else special nowhere, maximizing the number of macros that'll know what to do with it.
At least, I like this kind of consistency as long as the language/utility is going for ease of customization ('cause of fewer cases to cover) and minimalism. When it comes to making code readable and writable, consistency can be less important.
---
A second rationale. As waterhouse found out (http://arclanguage.org/item?id=12612) arc is extremely permissive about letting us override things, so there's no point focusing on what is legal in this arbitrary implementation.
For a language people are eager to make breaking changes to for their own purposes, that's true. But several programmers trying to make interoperable code won't each make breaking changes independently, right? Arc may promote a philosophy of fluidity, but any particular version of Arc is still made immovable by the community that uses it.
---
A third rationale. Right now you want to disallow ? because it's not ssyntax. When it becomes ssyntax won't it still be unusable in arglists for the same reason & is unusable now? :) At that rate we'd never get syntax inside arglists. Which may be a plausible position, but then we should be arguing about whether arglists can benefit from syntax.
Hmm? You've got what I'm saying completely backwards. I think we should commit to having ? be completely special (a kind of ssyntax, for instance) as long as we want it to be special in parameter lists. Meanwhile, & is totally usable for special behavior in parameter lists, since it's already useless for normal behavior (variable names).
I think I'm ok with us as a community saying "this version of arc treats ? as ssyntax" without necessarily having any code to do so :) expand-ssyntax for ? is the identity until we come up with a use for it. "Use ? as a variable at your own risk."
(Probably a strawman.) What I don't want is to start reserving tokens or characters 'for later use' and enforcing that they can't be variables and so on. Common Lisp does this and it's stupid. Did you know you can't declare a function called type even though it doesn't do anything, hasn't done anything for the past 15 years? What a colossal waste of a great token!
https://github.com/akkartik/wart/blob/master/009core.lisp#L1
---
You've got what I'm saying completely backwards.
Ah yes, I did. You said &o is ok.
It does raise the question of the right way to setup ssyntax. Either we're making changes to expand-ssyntax everytime we want a special case to turn off ssyntax expansion or we're saying "leave & as is when it begins or ends a symbol."
But then you could argue that &o is still impinging on a potential variable name. It's starting to feel like arguing about angels and pinheads; why is it ok to pun + and & and : so they do different things in different contexts, but not use ? because of some speculative fear of potentially punning variable names?
Those are all questions I worry about. ^_^ I'm really itching to post about Penknife's syntax, since I think it does a good job of dissolving these issues.
Well, one thing I've occasionally considered is having string?b become [isa b 'string]. :-p That would only do so much though, since you can just define (def a- (type) [isa _ type]) and say a-!string.b.
A while ago I was thinking of using a?b to mean (b a), with the question mark acting as sort of a backwards-pointing period. Now, for Penknife, I'm using a'b for that purpose, since it's sorta an upside-down period and it doesn't require the shift key (or even reaching off of the home row). However, a'b is parsed as two expressions in Arc, so using a`b or a?b for this purpose would probably be easier.
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.
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=12561http://arclanguage.org/item?id=12858
"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.
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
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.
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.
"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.
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...
"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."
My take on the "breaking changes" issue is that there should be at least some APIs people can trust to be stable. A library in development could have both an unstable version of the library and a stable version that calls into the unstable one. (This would be more practical if Arc had namespaces.)
Of course, if you don't know what features people are going to find useful, it's not especially possible to design a stable API. It's an approach that makes more sense for code that's designed to be something in particular, rather than an experiment.
Such a generalized Arc translation system would be pretty infeasible, at least with a static analysis approach. If a library defines a macro and uses it, you need to at least partially execute the library, or else you don't have the macro output to analyze.
I think the "do it dynamically" idea, by which I assume you mean setting up the transformer so that it applies as the library runs, is the way to go. But then I'm not sure what idea you have for that approach that would help the original issue, which is reacting to breaking changes in upstream code.