Huh. Somehow that rep trick never occurred to me either.
One of my big motivations for first-class macros was to be able to organize my code in any order. If there's no external constraints it increases the odds that a given snapshot will have some reasonable organization that's easy for newcomers to understand. Any constraints just increase the odds of stale organization. I've ranted before about this: http://arclanguage.org/item?id=15587 (footnote 1); https://github.com/akkartik/wart/blob/master/001organization. If macros can be written in any order, that reduces a lot of my need for first-class macros.
Sorry to say, but I don't believe any of this actually lets you use macros before they're defined. Calling the macro's rep will just succeed at expanding the macro, not executing the resulting code (and not suppressing expansion and execution of the arguments).
Analogously to Pauan, I'd love to have you try wart. It's much slower and has fewer libraries than arc. But on the other hand it's cleaner, and has fexprs, python-like indent-sensitivity and python-like keyword args. At the very least, maybe worth a few minutes play? (And tell me what you think? :) Somebody using it may well be the impetus I need to go back to work on speeding it up.
Hello akkartik, I'm glad to be back. I've been lurking on and off for the past few years, but it will be nice to get involved again.
I guess I should be fair and try yours out too while I'm at it, though I have to say that the whitespace sensitivity makes me somewhat cautious. For some reason, I've always liked the more traditional sexpr syntax for lisp. Does wart mind if I use parens for everything?
Even more disconcerting would probably be your infix support. Maybe it won't be as much of a problem as I'm expecting, but I like being able to use random symbols in my variable names.
Also, numbered git commits strikes me as a little odd, as does the numbered source files. In the latter case its little more than taste, and I see that Pauan is doing the same thing. Maybe you have a good reason for it and I'd start doing the same if I only understood.
Anyway, enough casting of doubt on someone else's pet project. I'm certainly interested in a clean language with the possibility of fexprs to try out and maybe even keyword args. I'm not sure what I would actually need them for, but more power is never something I will turn down.
I'm also somewhat interested in the wat/taf projects, but they seem to be a bit more experimental right now.
--
As for my project plans, I'm thinking of doing two or three web service projects on the side, as a long term investment counter-point to my current hourly contracting job.
The first one I'm thinking of focusing on is a sort of easy, data-driven unit-testing-as-a-service concept. If you've ever seen the fit or fitnesse testing frameworks, this idea was originally based off of those. Instead of writing unit test code, you would use a website to input test values and corresponding expected results in a table format. The first row of the table specifies the function or object being tested and its arguments or property names, and each row after would give the values for that test case, with the last column or set of columns specifying the return value. Fitnesse did that for c# and java, but it had a few major flaws. In the first case, it would only interact with classes that inherited from the fitnesse test classes, so you were forced to write test harness code anyway. Second, the user had to format the tables manually using a wiki format, so it required a bit too much manual formatting and there wasn't any way to provide additional metadata or add any more dimensions to the tables.
The first few incarnations would probably be something really simple that would only be useful for testing code locally, but eventually it would be expanded to a web service that supports multiple languages and has a way to point it at any vcs repo and run tests interactively in the cloud. This would hopefully be a cheapish testing and specification solution for startups or foss projects that would be easy enough to add to existing code that people would actually do it. That and an enterprise version that can be deployed internally, which would hopefully make it so that business analysts and the QA team can write, run, and review the tests without having the dev team write a separate test harness for them.
There's a bit more to the idea, but right now none of it has been written, so I probably shouldn't advertise features that I may never get to, or might not even be feasible in the first place. Of course, I like talking even more than I like coding, so I'm sure we can discuss it if you're interested. In fact, I would be very open to discussion, as I'm sure what other people actually want/need/would be willing to pay for in a test system won't match up exactly my own ideas.
"In the latter case its little more than taste, and I see that Pauan is doing the same thing."
The reason I numbered them was just to make it easier to navigate the code. As a user, if you see "01 nu.rkt" you know it's loaded first, and that "02 arc.arc" is loaded second. And each one builds on the stuff defined previously, so you can read it in a linear order.
I only did that for the stuff that's automatically loaded when you start up Arc. You'll notice that the "lib" and "app" stuff is un-numbered. And I don't expect user-created code or libraries to use numbers! So I definitely don't take it as far as wart does.
Ah, I was unaware of fitnesse! Thanks for the pointer, that's a really neat idea. Tests are a huge part of wart's goal of 'empowering outsiders'[1].
Sucks that fitnesse is stuck in the java eco-system. Just porting it to lisp/arc/wart would be awesome for starters..
Arguably much of the benefit of testing comes from the same person doing both programming and testing. Organizations which separate those two functions tend to gradually suck. If you accept that, inserting a web interface between oneself and one's tests seems like a bad idea. Perhaps fitnesse-like flows would be best utilized to involve the non-programmer in integration testing, testing the entire site as a whole rather than individual functions. Perhaps script interactions with a (non-ajax for starters!) app so that the CEO/QA engineer doesn't have to know about REST and PUT/GET? Hmm, that would be cool..
Don't mind me, I'm just thinking aloud :)
[1] https://github.com/akkartik/wart/blob/531622db6a/000preface. I spend a lot of time thinking about this, and at half an opening will fill your ears about all the ways in which tests, whilst awesome, aren't sufficient. Along with ideas for complementary mechanisms.
Organizations which separate those two functions tend to gradually suck
Hmm... Well, that's certainly a valid opinion, and it may even be true in a lot of cases, however I think the issue is largely due to two other related issues: 1) The requirements aren't specified clearly enough, and are dissociated from the tests as well, and 2) they just don't have good enough tools.
Tests can serve many purposes. The most basic, given the fundamental concept of a test, is to tell you when something works, or when it doesn't. TDD focuses on the former, unit testing and regression testing focus more on the later. Tests can be used at different points in the development cycle, and if you use the wrong kind at the wrong point, it's not really all that helpful.
My concern is that its too difficult to write the correct kind of test, so most developers use the wrong kind or don't use any at all. There's nothing really wrong with that, I think it's just an unfortunate inefficiency, like trying to debug without stacktraces. >.> Hmm. Something to look forward to going back to arc I suppose. Anyway, my goal is to make testing easy enough to do, either for developers who just want a quick way to check if they broke something after making a 'minor' change, or for larger companies that want to know that all their requirements have actually been met.
So, to solve the first problem I'm hoping to utilize a lot of reflection and code inspection so that at least the outline of the test cases can be generated automatically, if not more. Then it should be really easy for the programmer to just add the missing details, either as specific test vectors or by using a more general definition of requirements using something like QuickCheck's generators.
In the long run the plan is for the tool to be able to support working the other direction, from requirements to tests. Hopefully with better tool support, and more intelligent interaction with the system under test, it should be possible for the architects to specify the requirements, and the tool should be able to verify that the code works.
Yes, divorcing tests from code could mean that different people do them. Doesn't have to be the case, but it becomes a possibility. And that means that they could will have a different perspective on the operation of the system, but not necessarily a worse one. If it's the architects or BAs writing the tests, then they might actually have more information about how the system should be working than the programmers, especially in the case that the programmers are outsourced. At which point allowing someone else to write the tests is an improvement. When developers write the tests, it doesn't help if they use the same incorrect understanding of the requirements for both the tests and the code.
Hopefully by making an easy enough tool that supports rapidly filling in tests based on code analysis (which would help anyone that doesn't know much about the actual code base match it up with the requirements they have) reducing boiler plate and barriers to testing, making it a much easier to use tool for developing. Maybe if it gets easy enough, developers would find that testing actually saves enough time testing to be worth the few seconds specifying test vectors for each method. And if it can do a good enough job at turning requirements into tests in a way that is clear enough to double as documentation, it should save the architects and BAs enough time, as well as make implementation easier for developers, that I might actually be able to sell licenses :P
"If it's the architects or BAs writing the tests, then they might actually have _more_ information about how the system should be working than the programmers,"
Oh, absolutely. I didn't mean to sound anti-non-programmer.
I tend to distrust labels like 'architect' and 'programmer'. Really there's only two kinds of people who can work on something: those who are paid to work on it, and those who have some other (usually richer and more nuanced) motivation to do so. When I said, "Organizations which separate those two functions tend to gradually suck", I was implicitly assuming both sides were paid employees.
If non-employees (I'll call them, oh I don't know, owners) contribute towards building a program the result is always superior. Regardless of how they contribute. They're just more engaged, more aware of details, faster to react to changes in requirements (which always change). Your idea sounds awesome because it helps them be more engaged.
But when it's all paid employees and you separate them into testers and devs, then a peculiar phenomenon occurs. The engineers throw half-baked crap over to testers because, well, it's not their job to test. And test engineers throw releases back at the first sign of problems because, well, it's not their job to actually do anything constructive. A lot of shuffling back and forth happens, and both velocity and safety suffer, because nobody cares about the big picture of the product anymore.
(Agh, this is not very clear. I spend a lot of time thinking about large organizations. Another analogous example involves the patent office: http://akkartik.name/blog/2010-12-19-18-19-59-soc. Perhaps that'll help triangulate on where I'm coming from.)
(BTW, I've always wondered: what's that cryptic string in your profile?)
Don't feel like you have to be fair :) The world isn't fair, and I understand about differences in taste. My goal with wart and some other stuff has been to figure out how to empower outsiders to bend a codebase to their will and taste with a minimum of effort. For example, one experiment I'd love to perform on you is to measure how long it takes you to fork wart to toss out infix and get back your beloved hyphens :) But no pressure.
Thanks Kartik. My git knowledge is about the level where I can pull and push to github and resolve minor conflicts, so this is probably a really basic question -
I know how to install git, but how hard is it to set up a git server to receive the remote pushes from dev?
Oh, well I got a lot from it. :) I may have quoted you out of context just now, but I think your post was the first time I thought about the idea that optional args and Haskell-like currying may encourage opposite argument orders. If a function argument is going to remain mostly constant, it should go last so it can be optional, but it should also go first so we can curry it away.
Since then, I've come to think the conundrum is largely internal to currying itself: Currying is useful when people have a frequent need to insert their own locally constant value. That's a tenuous balance in itself, and only one half of it conflicts with the general idea of putting stable values toward the end.
At the moment, I just say no to currying. I even manually eta-expand higher-order applications so it's easier to see what function signatures I expect, and so they're easier to step through in a debugger.
// No:
_.arrAll( arr, _.isString )
// Yes:
_.arrAll( arr, function ( x, i ) { // or just ( x )
return _.isString( x );
} )
"Hmm, at the very least, perhaps ... (as.int "34")"
I had thought about that for Penknife (http://www.arclanguage.org/item?id=13715). I was going to call it "ify" and then use it with the reverse application syntax:
However, I still don't see any semantic benefit in associating a coercion function with a type tag. This would have been nothing but a way to shove a bunch of different utilities into one organizational unit, like Java's static methods. And an organization style like this can backfire: If the system is trying to be securable according to the principles of object-capability model, a programmer who passes a whole open-ended bundle of utilities into untrusted code might accidentally expose more privileges than they've bargained for.
---
"though I know you prefer recognizers to types"
I was thinking of bringing that up in response to the OP, but I think I'm mostly on the side of type tags now! I use tables with "type" fields all the time. I like the ability to dump these tables at a REPL, serialize them, use 'iso for deep comparison, or pass them between frames (in JavaScript). This could be a mess if I use more of these features than I plan to support, but "adding" support is as easy as changing my mind. :-p
My old argument for maintaining a dedicated (foo? x) procedure was so that the 'foo? symbol could be namespaced just like any other member of the global scope. But if the type needs to go outside the language runtime and into serialized data or other concurrently executing runtimes (namely, browser frames), then runtime-local tools for namespace management aren't much help.
Fully abstraction-leak-proof namespace management amounts to having a secure notion of which programs have privilege over which namespaces. Cryptography makes it possible to achieve a pragmatic degree of security at the level of serialized data. I've been keeping this in mind as a guideline during my recent module system pursuit.
Ah, thanks for the report! That was my fault. This bug has been in anarki for over a year, ever since I messed with its templates: http://arclanguage.org/item?id=15664. In particular this highlights an issue with change a) at http://arclanguage.org/item?id=15690: when the default value is an expression like (seconds) for the time field, it's pointless not to inline it.
This is now fixed (as of https://github.com/nex3/arc/tree/3d643ea924). I ended up backing off on changing defaults; arc now goes back to providing no support for them whatsoever. It's too hard to track default values while we wrap exprs in functions.
It's also been a constant enough question in my mind how the current semantics differ from arc3.1 that I wrote up a report.
This is harder than I thought. Things will likely change as I continue to work on this, so that templates written to disk now might not be quite what you get back after a git pull in future. If you put your email in the about section of your profile I'll notify you when that happens, and exactly what changed.
"It's a good question whether you ever need cycles outside of these two scenarios."
I suppose there might be some algorithms that work better with mutable conses, but honestly, I think conses work best when they're functional, since they have a recursive definition. Making them mutable muddles a lot of different things with no real gain because you rarely mutate conses in Arc.
1. Using a hash table for cycle detection can get prohibitive for large cycles. Fortunately there's Floyd's algorithm which detects cycles without the space overhead: https://en.wikipedia.org/wiki/Cycle_detection#Tortoise_and_h.... Here's how you might print an infinite list, one item to a line:
-- z.wart
def (print x hare)
if ~list?.x
prn x
(addr.x = addr.hare) # like arc's is
prn '...
(do (prn car.x)
(print cdr.x (cdr+cdr (or hare x)))) # + is compose
# create a cycle
(<- x '(1 2)
lastcdr.x x)
prn (firstn 5 x)
print x
To run it:
$ git clone http://github.com/akkartik/wart
$ cd wart
$ git checkout f171a9d878 # Unnecessary except for visitors in the far future
$ ./wart
ready! type in an expression, then hit enter twice. ctrl-d exits.
load "z.wart"
(1 2 1 2 1)
1
2
...
2. You mentioned a couple of possible notations for cycles in lists. Here's my suggestion:
(1 2 ...) # means (1 2 1 2 ..)
This works especially well because wart uses ... for dotted lists:
(1 ... 2) # read as '1 consed with 2'
(1 2 ...) # read as '1 2 consed with itself'
Finally, you could represent more complex cycles as well:
Both of the things you're talking about look specialized for cycles in flat lists. I think dido's examples could have just as easily used 'scar instead of 'scdr.
arc> (= x '(1 2))
arc> (scar x x)
((...) . 2)
#0=(#0# . 2)
I do like the (1 2 ... (3 4 ...)) notation for cyclic lists though. :)
The Kernel R-1RK has a good approach to treating flat cyclic lists as though they're a usual case. IMO, it means everything that deals with lists is more complicated to explain and arguably doesn't even deal with "lists."
Well, now that I consider it the real reason why I missed these cycle detection algorithms is that I've never thought of conses as being just lists. Sure, they're general enough to represent lists, and binary trees, but with the presence of scar and scdr, they are also capable of representing directed graphs with vertices of at most degree 2. In general, any Arc variable can be considered as a reference to a general directed graph (hash tables in particular can be considered vertices of arbitrary degree). Thus, pretty-printing an Arc variable boils down to traversing the graph it represents, and the traversal order that seems like it makes best sense here is depth-first search.
To do the #0=(#0# . 2) notation though seems like the traversal needs to be done twice, first to get the numeric assignments, and next to actually print them. Not sure if it can be done in one pass without making a tree of prettyprint fragments. A marshalling algorithm will need to do it that way though, I should think.
You got me rereading R-1RK, and I was reminded again of how much I like the way Shutt connects up his design decisions to design goals and constraints. It's very in the spirit of Christopher Alexander (http://www.amazon.com/Notes-Synthesis-Form-Harvard-Paperback...).
In spite of the level of detail, kernel's design goals diverge so quickly from my own that I can't reuse any of the design work. It's really too bad.
Today's frustrating example: I really couldn't give a rat's ass that Kernel permits cyclical argument lists in operatives but not applicatives. Then in A.4 he talks about what's missing before R0RK, and the support for cycles feels pedantic next to the problem of a fast implementation. If he wasn't so concerned about handling cycles during eval perhaps the code wouldn't be slow. How the heck can a language be slower than wart?!
I'd assumed I could apply Floyd's hare to handle cars as well, but you're right, now I'm not so sure. The key difficulty is that there's two dimensions, so does the number of hares double each iteration?
Here's a flat-cycle-detecting iso I've been playing with:
def (iso a b)
((afn ((a harea|through) (b hareb|through))
(if ~list?.a
(a = b)
(addr.a = addr.harea)
(addr.b = addr.hareb)
:else
(and (iso car.a car.b) # Can't detect cycles through car.
(self `(,cdr.a :through ,cdr+cdr.harea)
`(,cdr.b :through ,cdr+cdr.hareb)))))
(list a :through cdr.a)
(list b :through cdr.b))
"The key difficulty is that there's two dimensions, so does the number of hares double each iteration?"
Hmm. As the tortoise goes down branches, its location becomes indeterminate. As the hare goes down branches, its location becomes indeterminate relative to the tortoise, so we might need multiple hare markers per branch if we're doing a tortoise-directed search. If we try using the hare to direct the search instead, then we may still need to keep a growing history of hare locations on each branch so the tortoise can follow.
Either way, keeping a hash table sounds more straightforward to me.
Are you using this algorithm properly? I haven't seen any of your example code do a second pass to find out where the cycle begins and what its period is. But maybe you don't need to...?
I'm just taking a constant-factor hit :) A few extra iterations doesn't change the result for either print or iso. And for cycles that loop back all the way to the start it turns out there's zero overhead.
x <- '(1 2 3)
lastcdr.x <- x # 1 2 3 1 2 3 1 ...
print x
1
2
3
... # exactly one iteration
Here's a slightly different bug I found before your update. It's another case where I don't know what behavior you expect, but it probably isn't this. :-p
(do (a <- '(1 2 3)) (lastcdr.a <- cdr.a) nil)
=> nil
(do (b <- '(1 2 3 2 3)) (lastcdr.b <- cdr.b) nil)
=> nil
(not+not+iso a b)
=> nil
(not+not+iso b a)
=> 1
(I'm using not+not+iso so it returns a predictable value rather than an address.)
Interesting. I'd been looking around for cycle detection algorithms and knew I might have forgotten a simpler algorithm. I think that would do very nicely for the pretty printer and most likely the marshalling algorithm as well. We'll see if I can find a way to adapt it to work for iso as well, though on the face of it, it doesn't look that straightforward.
As it happens I provided an implementation of iso at http://arclanguage.org/item?id=17365 :) Let me know if my toy language isn't clear. The basic idea is that you need two hare pointers, one for each arg.
I don't know what my point was in posting that link--I mulled over it for a long time before just posting what I'd typed in--and now I don't understand your reply. What did you take off the table, and when? ^_^;
:) I assumed you were saying that wart wouldn't be able to include '+' in symbol names, but that was already true before this change. Wart has other constraints, but if you can use '&' for something you can also use '+'.
Does that trigger memories? Did I understand you right? Were you nostalgic for the good old days when wart symbols could include '+'? ^_^
"Should exact or inexact be the default subtype for num?"
I see at least a few good solutions:
1) Use floating point everywhere. This is what JavaScript (and Nulan) do. Simpler, but it means you give up exact math on rationals.
2) Use floating point by default but support coercing to exact. This may or may not lose information, I don't know. Alternatively, support exact by default and coerce to floating point.
3) Do things the way Racket does it, but when printing the number, coerce it to floating point. This is what Arc/Nu does:
> 3/2
1.5
This has the benefit that math calculations are exact, but the result is printed in a more readable fashion (more readable to me anyways).
Thanks, I ended up going with the third option, but with a twist: display prints inexact but write prints exact. So at the prompt you still see exact numbers:
Well, the whole reason I did it in Arc/Nu was because I liked to use the REPL as a calculator, and I hated having it print rationals. So, having the REPL print rationals kinda defeats the point in my eyes, but I don't care since I don't use Anarki.
Yeah, I don't actually want to see rationals at the prompt. It's just that lisp has this nice distinction between 'write and 'print, with the guarantee that 'read can handle anything 'write emits. I'm loath to have write+read change a value in subtle ways.
In this case, that the repl uses 'write is an unfortunate constraint. Still thinking..
Since that's being done in Anarki, I'd rather use $.exact->inexact, rather than relying upon Racket's auto-coercion. My trick is best used in Arc 3.1 which doesn't have (easy) access to Racket.