I think it is really, really bad to have to type "(do x)" instead of "x" just to avoid macroexpansion. Additionally, I think it is unnecessary: you'd only have to do that because arc3.1 happens not to check for lexical variables when determining whether to macroexpand. This is different from the way it works in Scheme and Common Lisp, and I don't think it was a deliberate design decision, and it is really easy to change:
So just treat it as a bug in arc3.1--it probably won't even cause problems most of the time, because it's kind of a rare case--and fix it in your own ac.scm, and assume that it will be fixed in future users' Arc implementations. Please do not establish "(do x)" as good coding style. If the compiler screwed up whenever you had a variable that had three vowels in a row, the solution would be to fix the damn compiler, not to awkwardly twist up your own code; and if other people were using the old broken compiler, you'd first tell them to upgrade, and only change your own program to cater to the bad compiler if it was absolutely necessary for some reason--and you'd do so after you'd gotten your program the way you wanted it.
Edit: It is probably a hard problem to diagnose if you just leave it there and someone uses it and is like "why is this screwing up?". So if you intend for others to use it, you could put in a thing like this:
(mac achtung (x) `(+ ,x 2))
(let achtung [+ _ 5]
(unless (is (achtung 0) 5)
(err "Oh god you have a bad Arc compiler. Fix that crap:
http://arclanguage.org/item?id=13606")))
> So just treat it as a bug in arc3.1--it probably won't even cause problems most of the time, because it's kind of a rare case--and fix it in your own ac.scm, and assume that it will be fixed in future users' Arc implementations.
I'm all too happy to do that. ^_^ Note that it'll probably mean I've introduced bugs to Rainbow and Jarc. :-p
Part of the reason I've harped on the state of affairs that causes me to write (do x), as well as the reason the state of affairs isn't altogether bad (that it makes macros that generate macro forms a tiny bit less susceptible to variable capture), has been in the hope that people will become confident that it's really annoying. ^^;
I honestly didn't know I'd be the only one actively defending it (rather than merely leaving things the way they are or upvoting), so I continued to give it the benefit of the doubt. Let's not do that. :)
> it probably won't even cause problems most of the time, because it's kind of a rare case
Actually, I use local variable names that clash with macros all the time, and that's why I started my (do x) and (call x) patterns in the first place. :) If I remove the cruft right away, my code will almost certainly break until everyone has upgraded. Dunno if anyone but me is going to suffer from my (published) code breaking, but hey. :-p
---
By the way, in this case, I was programming to a different core language implementation--in fact hacking on something core to that implementation--and I admit I cargo culted.
I was hoping to actually try running some code last night, but no dice. Anyway, that sounds like it would in fact be a problem. In fact, in Arc 3.1 at least, 'defset things are looked up at compile time, and 'get is given special treatment. Try something like this:
(= foo (table) bar (table) baz (table) qux (table)
quux (list nil nil))
(mac get1 (x) `(,x 1))
(let ref bar (= (ref foo) 2))
(let get idfn (= ((get baz) qux) 3))
(let cadr car (= (cadr quux) 4))
I wouldn't worry about 'get too much. It's inconsistent in just the same way as metafns are, so it's just one more in a list of names we shouldn't overwrite or use for local variables.
The setforms case is a bit more troublesome. Maybe they shouldn't be macro-like, and should instead be special-case extentions of 'sref? If the return value of 'get were its own type with 'defset and 'defcall support, that case could be eliminated too.
This may be a case where I've painted myself into a corner with wart. Since wart is built atop a lisp-2, I can't just expand the ssyntax a.b to (a b). I have to generate (call a b). Since I want to be able to say things like ++.a, I need call to handle macros as well. But now (call f x) will try to call a macro called f before it looks for the lexical binding.
It's not as big a problem as it may seem. You don't have to worry about future macros shadowing lexical bindings as long as they load afterwards.
The biggest compromise I've had to make because of this: using call-fn (which doesn't expand macros) in accumulate (https://github.com/akkartik/wart/blob/ed9a7d4da1fa017188fce2...) because I wanted to name one of the keyword args unless. So you seem to be watching over your creation after it's left the nest :)
(tangent)
I spent an embarrassingly long time trying to have lexical bindings override macros, before realizing that's impossible in wart: macros get expanded long before lexical bindings are created. So this is a case where you really need a full interpreter; any macro you write can't inspect lexical bindings up the call stack. (oh, for python's nested namespaces..)
(even bigger tangent)
Wart update: arc.arc is just about done. I'm going to start on the webserver, probably not srv.arc but palsecam's http.arc (http://www.arclanguage.org/item?id=11337).
I ended up dividing up ac.scm into 17 files, and arc.arc into 26 (the boundary is fuzzy). So far each file seems highly coherent, and most files are short, so the codebase feels the way the old-timers described forth code: "A component can usually be written in one or two screens of Forth." (http://prdownloads.sourceforge.net/thinking-forth/thinking-f..., pg 41; screens are Forth's units of code.)
> I ended up dividing up ac.scm into 17 files, and arc.arc into 26 (the boundary is fuzzy). So far each file seems highly coherent, and most files are short
Ok, I started reading wart. :)
Skimmed that Forth reading. I've only dabbled in Factor, but I'm sometimes tempted to explore concatenative programming more in depth.
I think there are only two conceivable, allowed-by-the-language cases where you could have dotted lists in code: (a) literal dotted lists, as in '(1 . 2), and (b) parameter lists. [Edit: Oh man, I forgot about destructuring, as rocketnia points out. I do sometimes destructure with dots. They are instances of parameter lists, though, and can be handled as such.]
(b) can be easily handled in other ways (e.g. in CL, you say "&rest args" instead of ". args").
(a) is extremely rare, and it's never necessary in code--you could replace it with (cons 1 2). There is the fact that, inside the body of a function, (cons 1 2) will create a new cons cell every time the function is called, whereas '(1 . 2) will always refer to the same cons cell[1]; however, you could fix this by wrapping it in (let u (cons 1 2) ...) and referring to u instead of '(1 . 2).
The only potential problem I see (with dropping dotted lists) is that you might need to read (or print) dotted s-expressions as data. That does come up now and then; you might represent some compound data structure (like a rational number) with a cons cell, which is slightly more efficient than using a two-element list (two cons cells) and perhaps nicer. Also, Racket prints hash tables with dots--I'm sure you routinely see things like #hash((a . 1) (b . 2)). And someone might create some kind of tree structure with conses.
You could dotted lists for now, then, when you find them necessary, create a private notation for non-proper-list trees (say, using ^ in place of . in trees). Then there would be no problem except dealing with tree output from other Lisp implementations, which is rare enough that you can probably afford to ignore it for now, and if it comes up, just write something to translate back and forth. That would be my advice.
As for unquote-splicing: Most of the time I use it in macro definitions, most of the time for "body" arguments. Here is a handy terminal command to illustrate:
If what you're wondering is whether I use unquote-splicing to make dotted lists, the answer is no, and I would consider it bad practice (if you're making something as unusual as that, you should use cons or something).
Regarding (= 'foo 42): I do think it should be possible to perform assignment on symbols that are chosen at runtime. In Common Lisp, this would be (setf (symbol-function 'foo) 42); in Racket, this is (namespace-set-variable-value! 'foo 42). It's possible to do (eval `(= ,x ',desired-value)) [the ' is necessary because the Arc evaluator doesn't like objects in code], but this feels like a hack and there should be a built-in way to do it (I've said this before, by the way).
What should it be named, and if we wish to use the setf pattern, what should we name the corresponding analogue of "symbol-value"? At the moment, (eval x) does the same thing as (symbol-value x) when x is a symbol [bad things might happen if you do this to 'nil, but otherwise, yeah]. Would (= (eval x) ...) be fine? Or should there be a specialized operator named something like "symbol-value"? For purposes of conservatism, giving a meaning to (= (eval x) ...) seems easiest...
I immediately thought you were suggesting a way to set local variables at runtime, but this way I have a better answer for you. ^_^
Lathe already defines (= global.x desired-value) so that it behaves just like (eval `(= ,x (',(fn () desired-value)))). As an everyday expression, global.x behaves like (bound&eval x), which makes (zap _ global.x) a bit more intuitive. This utility works on Arc 3.1, Anarki, Rainbow, and Jarc.
I don't mind if something like this is added to the language core too, but isn't that less axiomatic?
It is certainly less axiomatic--you don't need this to have a working Lisp. However, it's useful (I needed it to do my little module system), it's likely to be very simple to implement as a primitive (given access to Scheme, we can just use 'namespace-set-variable-value!), and its name clearly maps to its function and its setf behavior. (I'd be inclined to use the name "global-value" or "symbol-value", actually.)
Finally, it's easier to see "symbol-value" written in the language description and use it than to figure out how to use the 'eval method properly. Just look at how complex your definition is; even I, who do see why it's necessary to go ',desired-value instead of ,desired-value in my version [if objs in code worked properly, you could use the former version for a while without noticing it was wrong], I don't see why it's necessary to go (',(fn () desired-value)), and I certainly wouldn't think of it myself. (I still don't believe it's actually necessary, but I won't press the point.) Like the case of (++ (car (pop xs))), this is one of the things a core language is good for: the implementor has considered and avoided all the subtle pitfalls for you. (Actually, implementing it as a primitive with the "namespace-..." functions, there aren't any pitfalls to avoid.)
By the way, I don't know if the core of the language is supposed to be all that axiomatic. It should be clean, certainly, but by its nature it should provide more than minimal axioms. In an essay[1], Paul Graham talks about "the core language—those operators that are neither primitives like car and cdr, nor special-purpose library functions. I mean operators like CL's mapcar, let, remove-if-not, and so on."
At any rate, here's what I think: (a) There will be people who need the equivalents of 'symbol-value and (setf symbol-value)--people who write module libraries or debugging facilities or for whatever reason[2] want to determine names at runtime. (b) This might be enough by itself to warrant placement in the core language, but (c) it is extremely easy, not to mention efficient, to implement these as primitives, while it is subtle, error-prone, and inefficient to implement it with 'eval. Therefore, it should be built into the language. (Technically, if it's a primitive, then it's not part of the core language by the implicit definition from the essay, but whatever you call it, it should be built in.)
I feel better about rambling/ranting when I also produce something tangible, like working code. So, here you go:
That's why it's in Lathe, too. ^_^ Since it's there to implement the module system, it's one of only a couple dozen Lathe utilities that isn't in a module, but I find it useful enough that I like it that way.
I don't see why it's necessary to go (',(fn () desired-value))
If you embed a literal vector in an expression (for instance, a tagged value), Racket will evaluate that expression as a copy of the vector. So (let foo (annotate 'a 'b) (= global!x foo) (is global!x foo)) would return nil, which I find frustrating. ^^ (There might be significant mutations-aren't-reflected-in-both-copies gotchas too, but I'm not sure right now.)
If you embed a procedure instead, I don't know if it copies that too, but it doesn't matter 'cause a copy of the procedure should still close over the same bindings. In practice, (let foo (annotate 'a 'b) (= global!x foo) (is global!x foo)) works, so I'm satisfied.
By the way, I don't know if the core of the language is supposed to be all that axiomatic. It should be clean, certainly, but by its nature it should provide more than minimal axioms.
This is a point I just acknowledged but challenged at http://arclanguage.org/item?id=13266. I'm not sure entirely whether and how I (dis)believe it, so a response is welcome. ^_^
it is extremely easy, not to mention efficient, to implement these as primitives, while it is subtle, error-prone, and inefficient to implement it with 'eval.
Sure. Implementing 'symbol-value in terms of 'eval is a pretty big abstraction inversion, and it causes lots of unnecessary consing and compilation at runtime. I'm kinda conviced. ^_^
working code
Incidentally, official Arc allows you to say (assign a.b t) and observe (bound 'a.b). Your 'symbol-value makes it possible to get the value of 'a.b again, whereas there's no non-exploit way to do that with 'eval. ^_^ (You can use the (cdr `(0 . ,_a.b)) bug, lol.)
Speaking of 'bound... why is 'bound a primitive when 'symbol-value isn't? XD Actually, I've wondered why 'bound was a primitive for a while, but that kinda-convinces me further. ^_^
> What I really think is best is to just have both. ... I think that's generally why I make so many macros that are merely cheap abbreviations of functions, actually; I like calling the function versions directly every once in a while, but I like saving a bit of line noise in the common cases.
I agree with this. Compare it with having naming and anaphoric macros: iflet and aif, rfn and afn, even fn and []; I'd add awhile and whilet to the list even though awhile isn't in official Arc, and fromfile, tofile, and ontofile[1].
In fact, it is a very general practice in (functional?) programming to first write a function that handles something basic, then write another function on top of it that's more pleasant to work with. It really is ubiquitous. It's good because it separates the primitives from the high-level interfaces; if you want to change the primitives, you don't need to concern yourself with the high-level interface, and if you want to change the high-level interface, you don't need to concern yourself with the primitives. And macros give you flexibility, the ability to construct pleasant interfaces in ways you can't do with pure functions; for example, functions must evaluate all their arguments once, from left to right, while macros don't have to.
In this case, I don't think we have a function that directly does what you're asking for (i.e. it operates like obj except it evaluates the keys). I consider this a problem. Note that in Scheme and CL, we can go (vector x y z) instead of crap like (let u (make-vector 3) (= u.0 x u.1 y u.2 z) u). We should likewise have a good table-constructing function even if it isn't strictly necessary as a primitive. At the moment, this is the closest we have:
(apply listtab (pair:list 'a 1 'b 2))
=
(obj a 1 b 2)
So I think we should have a function like this:
(def ???? args
(apply listtab pair.args))
What should we call it? I think it's rare enough to want evaluated keys that we don't need to give it a short name like "obj". Maybe "make-table", "make-hash", "make-obj"... Hmm, there's a function named "table". Given no arguments, (table) returns an empty hash-table. We could make this be our table-constructing function--in fact, I think that makes a lot of sense, as the function defined above should return an empty table when given no arguments. I again draw your attention to the analogy of the vector function in Scheme and CL. Only problem is, the table function currently defined in Arc actually is overloaded: if you give it an argument, it will apply this argument (which should be a function) to the table before returning the table.
I grepped Arc for '(table ' and found three instances (all in news.arc) where the function table was actually called with an argument [the other matches were, like, (tag (table width "100%" cellspacing ...))]. They look like this:
(table
[each ip ips
(= (_ ip) (dedup (map !by (+ (bads ip) (goods ip)))))]))
(table [each-loaded-item i
(awhen (and i!dead (check (sitename i!url) banned-sites*))
(push i (_ it)))]))
The raw-Arc way to do this would be
(let u (table)
(each ip ips
(= (u ip) ...))
u)
and I believe Anarki has a w/table macro that would make it look like this:
(w/table u
(each ip ips
(= (u ip) ...)))
Which, I think, is close enough to the original that PG/RTM (whoever wrote the above code) wouldn't be too displeased if we kicked out the old usage of table. Actually, you know, this alternating-pairs proposed usage only applies when the number of arguments is even; it wouldn't even conflict if we wanted to implement both behaviors: 1 arg -> empty table, even args -> alternating pairs. In fact, it would simplify implementation to do alternating pairs until there are less than 2 args left, and then if there's 1 arg, treat it as a function and apply it to the table, while if there are 0 args, just return the table.
So. Options: (1) Have table do alternating key-value stuff; if there's one argument remaining at the end, apply it to the table; then return the table. (2) Have table do alternating key-value stuff; signal an error if there's an odd number of arguments [and Paul Graham can get used to w/table], or (2a) if there's one argument, apply that function to the table, while if there are an odd number ≥3 of arguments, signal an error. (3) Leave table as is, and find a new name for our function.
I think (1) is the best thing to do at the moment, and (2) is probably a better future goal but I would be fine being overruled on that (its single advantage is error-reporting when you accidentally give table an odd number of arguments). I think (2a) is a stupid hack and (3) is bad. As such, here is an implementation of (1). Replace the old '(xdef table ...' definition in ac.scm with the following:
I gave two ways we can already accomplish the behavior somewhat conveniently even if we don't have something sensible like your version of 'table:
(copy (table) 'a 1 'b 2)
(listtab:pair:list 'a 1 'b 2)
(Actually, I used tablist in that post, which was a bug.)
I could have sworn the Anarki version of 'table allowed for (table 'a 1 'b 2) at one point (which inspired me when I made 'objal and 'tabal for my static site generator), but I looked at the Git blame and didn't find any obvious evidence of that....
Oh, nice. I am not familiar with the copy function, but I missed that "listtab:pair:list" idea. Clever. I'm sure you agree, though, that we should have a nice table function, in the same way that we shouldn't have + defined for us but have to define - ourselves:
(def - args
(if (no cdr.args) ;or no:cdr.args with my recent hack[1]
(* -1 car.args)
(+ car.args (* -1 (apply + cdr.args)))))
I honestly wouldn't mind very much if '- were omitted. >.>; Actually, wait. Arc puts every global in the same namespace, so we'd have a few conflicting versions of '- in our respective personal utilities (assuming we don't all just grab the Anarki version), and that would be somewhat horrible.
But yeah, 'table is much better as a function that can actually construct complete tables all at once. ^_^ In this case, it's not a matter of name conflicts between libraries; it's a matter of the most obvious names being taken for the wrong behaviors. Namespaces are still a potential solution, though.
Namespaces solve the problem flawlessly only if you plan for no one to work on or read anyone else's code, ever.
Taking '- as the example: there are a few non-obvious things about it (how it works with more than 2 arguments, or how it works with 1 argument), and people could reasonably end up with different versions of it (I do think the current version is best, but I wouldn't blame anyone for not thinking about the 3-or-more-variable case or for implementing it differently).
This is not a problem if you use '- as a utility in the definition of your library functions and someone else just imports the exported functions of your library and uses them. But if you intend to, say, paste code on the Arc Forum and expect anyone to understand it, or if you want people to be able to download the source code of your library and make improvements to it, then there's a cost to every non-standard definition you use: the reader has to learn it. If the language doesn't provide "map" and "let" and "with" (which are certainly not primitive; in fact, they're defined in arc.arc), then either you don't use those operators (and your code will suffer for it), or you do use them and that's one more thing the reader needs to learn to understand your code. It's not the end of the world, it's not a game-breaker for all projects, but it's one more obstacle to people understanding your code, and if you can get rid of it, then that is all to the good.
This is why getting people to agree on a good common core language is a good thing even if the language supports namespaces.
...getting people to agree on a good common core language is a good thing even if the language supports namespaces.
That's a good point. ^_^ I don't use Anarki- or Lathe-specific stuff in my examples if I can help it. And I recently mused on[1] the trend for a language to incorporate a lot of features in the standard so as to improve the common ground, so I should have seen that point to begin with.
Namespaces solve the problem flawlessly only if you plan for no one to work on or read anyone else's code, ever.
I've sorta been banking on the idea that everyone programs to their own languages of utilities anyway, and that each of those languages can be talked about, accepted as a common topic, promoted by its proponents, and standardized just like language cores can. Certainly CMSes and game engines have those kinds of communities. :)
Multiple independently developed libraries won't always work together well enough to all be clearly canon components of the language's identity, but I think they are more likely to be interoperable than are things designed from the outset to be entirely new languages.[2] So I don't think it's important for the core libraries to be cutting-edge in practice. The core can fade into the background to a degree.
I haven't thought about this stuff very deeply yet, and it's not rooted in a lot of fact, so please feel free to disillusion me. ^^
[2] I don't mean to suggest there's a well-ordered expected value of interoperability. Whether a library is designed or used in an interoperable or impregnable way is sorta relative and idiosyncratic. One more avenue for interoperability could even be annoying if people come to depend on it in poorly prepared-for ways. ^_^
(I'm noticing there's a bit of a brevity boost with infix syntax, though. It means you typically only have to add "1 + " to an expression, rather than "(+ 1 " on one side and ")" on the other, for a total of one edit versus two. I guess that makes it a way to boost brevity in a less customizable language.
With ssyntax, you usually only have to add "inc:" or "inc." to the left side of an expression (depending on whether it's already a function/macro call or just a symbol). This is why I love the hell out of ssyntax. It only doesn't work if the expression is already of the form "a.b", in which case you have to add some parentheses. Currently, "inc:inc.2" expands to "(compose inc inc.2)".
Recommendation: Have "a:b.c" expand to (a:b c); this will make it always, or nearly always (I haven't thought much about using it with quote or quasiquote), possible to tack an extra function/macro call onto an expression with a single edit. I think it's also more consistent: currently we have a.b.c -> (a.b c) [-> ((a b) c)], which is useful for referencing nested data structures; and it fits that pattern to have a:b.c -> (a:b c) [-> ((compose a b) c) -> (a (b c))]. I really think this would be a big improvement.
Anyway, this ssyntax stuff has the major advantage that it works to tack on any single-argument function/macro, rather than just the very few that are important enough to have their own special character. I find this extremely convenient: I can add "time:" and "prn:" to an expression when I want to debug it, and then remove it just as easily. Combined with my 'cp function (http://arclanguage.org/item?id=12873), this has in my past made for some very pleasant debugging.
Aesthetically, tacking "func:" onto "expr" is like adding an element to the tree (in fact, it literally does "expr -> (list func expr) = (cons func (cons expr nil))"), which is an O(1) operation, and it makes sense for it to always be a simple O(1) editing operation. (Adding something to the left side, then remembering to look for the right side so you can add a close-paren, is not O(1) to me.) In fact, it's a common O(1) operation, and it should be O(1); and the same should go for its opposite, removing an element previously tacked on.
Imma go figure out how to change ac.scm. ...And I've kludged it by making . and ! expand before : and ~ in the definition of "expand-ssyntax" in ac.scm.
This should work fine for the near future, as I don't have any reason to mix "." and ":" in expressions other than with "." as the last ssyntax character. I think it should ideally expand right-to-left no matter what the ssyntax characters are; note that, under this scheme, "a:b:c" would expand to (compose a:b c) -> (compose (compose a b) c), which is equivalent to the current expansion (compose a b c), and the Arc Compiler could transform the former into the latter.
Arc seems to load properly, and the ssyntax works the way I like it.
In Arc I sometimes like to say (a.b:c.d e f), which this change breaks. But I don't mind that. ^_^
Anyway, what you're talking about is already how Penknife works: Infix operators on the right are always handled first. An interesting result is that a!b ssyntax is a bit redundant under this setup. Here's an Arc demonstration:
; with ! ssyntax
a.b!c.d -> (a.b!c d)
-> ((a.b (quote c)) d)
; without ! ssyntax
a.b:quote.c.d -> (a.b:quote.c d)
-> ((a.b:quote c) d)
-> (((compose a.b quote) c) d)
-> ((a.b (quote c)) d)
So now the following ssyntaxes are all abbreviations for things that are a little more verbose but just as edit-efficient:
In Penknife, I've been thinking about having infix operator such that a`b.c is (b a c). Arc could do this if a`b expanded to something like (opcurry b a), where 'opcurry was a metafn such that ((opcurry a b c) d e f) expanded to (a b c d e f). Then the only essential ssyntaxes would be a`b and a.b:
Of course, at a certain point the verbosity is a bit silly. I've defined ` as a curry function in Penknife so that I can test it out a`foo.b with everyday functions foo, like 1`+.2 and so forth, and I've found it pretty cumbersome to actually type. Still, it's less cumbersome than 1.+(2), I suppose. :-p
Maybe it'll be useful in an axiomatic way. Perhaps a Penknife-like or Arc-like language can have ` and . as its only basic infix operators, with all other infix operators being abbreviations defined in terms of those two....
Hmm, an alternate axiomatic approach is to treat a`b.c as a single ternary operator a{b}c.
a&b -> a{andf}b
a:b -> a{compose}b
a.b -> a{call-op}b ; where (call-op a b) expands to (a b)
a!b -> a{compose}quote{call-op}b
.a -> get{call-op}a
!a -> get{compose}quote{call-op}a
~a -> no{compose}a
This is essentially equivalent to Penknife's approach, except that Penknife uses Haskell-style naming rules (infix identifier or alpha identifier) rather than delimiters. So it looks like the a`b.c approach is just a way to simulate this syntax system within itself. Pretty interesting. XD
2. When you want Arc to do something, write an Arc s-expression to the ARC_ORDERS file. (Note that you'll have to restart this pseudo-REPL if an error occurs; you could use something like 'on-err or 'after to make that happen.)
This might be a horrible hack, but it's awesome, and it presents a very easy interface--e.g. if you have a program that lets you bind keys to shell scripts, then you can have a shell script say, like, "echo '(do-this-stuff)' > ARC_ORDERS".
Obvious drawbacks: It's slow compared to, say, a function call from the Arc runtime (you wouldn't want to use this as your graphics backend), and if two or more programs try to use this interface, hell might ensue (though you could assign them their own individual ARC_ORDERS2 and so on files; you could even make the number be a process id if you wanted). But it'll work fine for certain applications, and you may find it useful.
This is so similar in concept to what I'm doing with named pipes that I figured I ought to share it now:
First of all, you can create a named pipe in a directory with the command "mkfifo name".
Then, I have a shell script in mzscheme (should probably update to Racket soon) for launching arc that sets up the long running process with normal output redirected to a log file and a second repl redirected to two named pipes "in" and "out". The important part is
which could be written either in arc or Racket. In arc, it would probably be:
(w/stdin (infile "in")
(w/stdout (outfile "out")
($:tl))) ;whatever method you use to get to racket and call the repl
I'm not sure if that would work as well, since as far as I know there is no method in arc for setting the filestream buffer mode to 'none. If you have any issues, just use the racket version above.
Anyway, once you have the pipes set up, you can echo and cat them like any other file. The main difference being that they are treated exactly like a normal repl, and shouldn't have the overhead of sleeping and reading/erasing a file. I use a shell script to connect to them as a repl most of the time:
Macro: (as type x) : Macroexpands to (coerce x 'type). Leads to the more pleasant form:
(as string
(big long
(hairy gigantic)
(immense and complicated (function))))
instead of:
(coerce (big long
(hairy gigantic
(immense and complicated (function)))
'string)
In the first case, you can hear the coder thinking, "Now I want to coerce this thing into a string; this thing is big and long and hairy and [...]." In the second case, the coder thinks, "Now I want to coerce this thing into a string; I write coerce, and now I'll have to remember to write 'string; now this thing is big and long and hairy and [...]; and now what was that I was remembering? I see a coerce up there with the unbalanced parenthesis... oh, yeah, 'string." It's just a little bit easier on the mind.
(string:big long
(hairy gigantic)
(immense and complicated (function)))
No macro is needed, and there's less indentation and fewer parentheses. You can also improve on the 'as macro by allowing for (as (type expr)) in addition to (as type expr). That lets you cut down on indentation and parentheses just as much:
(as:string:big long
(hairy gigantic)
(immense and complicated (function)))
Sure is, but I'm just a bit of an un-fan of 'coerce. I've already mentioned unary functions being more convenient with a:b ssyntax. On top of that, there's oftentimes more than one obvious way to get one type of value from another, and while you could use custom types to represent more coercion targets, like (rep:coerce x 'assoc-list), I think that's a step in the wrong direction, just 'cause I don't know where the benefit is.
The ssyntax expressions (a:b ...), a.b, and a!b all pass only one argument to a, so ssyntax (currently) makes one-argument functions and macros more convenient than other kinds.
Eh? XD There's nothing wrong with that. My point was that it doesn't make a whole lot of sense to designate just one conversion for each target type when there are lots of options.
(Note from the future: What follows doesn't really continue that response. I just started randomly considering a few more angles. ^_^ )
Most of the time, [coerce _ 'string], [coerce _ 'sym], and so forth just end up being more verbose ways of saying things like [string _] and [sym _]. Almost all the uses of 'coerce in Arc 3.1 and Anarki are like that, where they hard code the target type, so that it's just like the name is coerce-cons or coerce-string but without any synergy with ssyntax. There are two exceptions:
(def sort (test seq)
(if (alist seq)
(mergesort test (copy seq))
(coerce (mergesort test (coerce seq 'cons)) (type seq))))
(def inc (x (o n 1))
(coerce (+ (coerce x 'int) n) (type x)))
These functions have the special property that they can be used with all sorts of types as long as 'coerce is capable of translating back and forth. If you want to make a custom type that's compatible with them, you only need to replace or extend 'coerce to do it, rather than, well, replacing 'sort or 'inc. :-p This technique was sort of rediscovered here recently: http://arclanguage.org/item?id=12340
Now that I think about this, my position has softened even more, but I'm still a bit stubborn. Even if 'coerce is only used as a way to convert something back from the type a calculation needs to manipulate it in, there's still an arbitrary choice being made between multiple behaviors. If some generic string utility wants to convert '(#\[ #\1 #\]) to "[1]" and back, but some JSON utility wants to convert '(1) to "[1]" and back, then (coerce "[1]" 'cons) needs to have two meanings at once.
Of course, if it comes to that, it's easy to just make the JSON utility convert from '(1) to (annotate 'json-encoded "[1]") and back instead. I think I have no escape from that. XD Any corner case I come up with is going to be at least as you're-not-gonna-need-it as 'coerce is. ^_-
Nevertheless, while I was arguing with myself, I came up with an experimental type-independent (and therefore 'coerce free) way to model back-and-forth transformations. It's a pretty obvious design, but I somehow managed to make it complicated and arbitrary in certain ways. :-p Here it is in case anyone's interested:
(def encode-int (unencoded)
(list `(type ,type.unencoded) int.unencoded))
(def decode-int (recovery-notes encoded)
(let fail (fn () (err "Unexpected 'decode-int case!"))
(case do.recovery-notes.0 type
(case do.recovery-notes.1
string string.encoded
int encoded
(do.fail))
(do.fail))))
(= encoding-int (annotate 'encoding
; This is more hackable than
; (list encode-int decode-int).
(list (fn args (apply encode-int args))
(fn args (apply decode-int args))))
(def fn-through (encoding unencoded body)
(withs ((encode decode) rep.encoding
(recovery-notes encoded) do.encode.unencoded)
(do.decode recovery-notes do.body.encoded)))
(mac through (encoding var . body)
`(fn-through ,encoding ,var (fn (,var) ,body)))
(def inc (x (o n 1))
(through encoding-int x
(+ x n)))
"it doesn't make a whole lot of sense to designate just one conversion for each target type when there are lots of options."
Ah, now I follow. But often there's one kind that applies far more often than alternatives. And it's good for coerce to give it. For other type combinations it's good to know that coerce is complete, and that it'll give some sort of conversion. But you're right, I don't know if this ever makes code shorter. Perhaps this is yet another example of programmer's OCD, that insidious need to have things be aesthetic even when they don't matter.
I just started randomly considering a few more angles. ^_^
I'm starting to realize that's the reason I have a hard time following what you write :) If I may make a suggestion, make a list. I find your long comments are often too terse. Often they contain enough stuff for three or four distinct postings or comments. But the transitions are abrupt, perhaps because you're trying not to be even more long-winded. If you gave each idea its own post or comment it would have more room to breathe, and I'd be able to return to each one at my leisure. Feel free to post new submissions with substantial ideas. We hardly have enough submissions here.
Like your code idea at the end of the comment, and the suggestion that it's like monad transformers. I'd love to see a post elaborating on it. I'd love even more to see a yegge-sized elaboration of all your thoughts on coerce and what 'synergy with ssyntax' means. Perhaps they haven't settled down yet, but keep it in mind when they do :)
Sometimes I feel I would spend an hour with any one of your ideas if you'd spent just a few extra minutes with it.
Hopefully this is constructive feedback. Perhaps it's just me having poor comprehension; you should ask for others' opinions.
My usual process is that I'll take a few hours to write a comment, then I'll run out of time and decide to come back later to finish it up, and then I'll never go back to that draft (but I might start from scratch on the same topic). Occasionally I'll finish something in time to post it.
Recently, I have less time than I'm used to, so I've been posting things even if they're rough, in case someone gets value out of it anyway. I guess this makes my posts into scatterings of half-ideas, based on things I've been thinking to myself but only now had a reasonable excuse to mention. (Usually I shoot for a few organized, mostly full ideas, which also have very good excuses to be mentioned. :-p )
Thanks to your suggestion, I'll try harder to talk about things even when there isn't an excuse. ^_^
(Incidentally, aw's thread doesn't show up in aw's "submissions" list. Weird.)
I noticed this as well as I was trying to recall some old ideas. For some reason people's submitted pages here don't get a more link like they do on HN. I emailed PG a request.
Macro: (aps x) : Returns a list of all bound Arc symbols for which x (coerced to a string) is a substring of the symbol (coerced to a string). Come to think of it, I'll make it sort them, too. Extremely useful if you wonder what precisely a function was named, or whether certain functions exist.
Implementing this requires access to the underlying Racket (or something similar in non-Racket Arc implementations). If, as in Anarki, ($ expr) causes expr to pass straight through the Arc compiler into underlying Racket, then the following implementation will work: