From: Ken A. <kan...@bb...> - 2004-04-01 19:42:30
|
JSchemer's, this is an interesting question that came up while i was reviewing a draft of Paul's book, "Hackers & Painters". http://www.oreilly.com/catalog/hackpaint/ At 04:08 PM 3/29/2004 +0000, Paul Graham wrote: >What causes flexibility? Is it simply succinctness? Succinctness is important, but its not the only issue. That JScheme is interactive is also very important, and a giant win over Java. Programming becomes very playful. A friend ,Mary, taught me that a ball is a great toy for a toddler, because you do something simple, like drop it, and you get a big payoff - it bounces and rolls in an unexpected way. Big suprise! You chase it, pick it up, and do it again. JScheme is like this,i don't program, i play. People ask me Java questions. I type JScheme and give them answers. Succinctness comes from several sources. Since JScheme does not need type declarations, coercions, and checked exceptions ..., typicaly, JScheme code is at least 1/3 less lines of code. This may not seem like much, but its like working 5.3 hours a day, rather than 8. However, if you simply write JScheme following a Java API, the lines of code counts can be similar. One reason is because of what i call "setter rafting style". A Java constructor can't take keyword arguments, so after you construct an object you may call setter methods on it For example, to make a red quit button that exists the application: JButton b = new JButton("Quit!"); b.setBackground(Color.red); b.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { System.exit(0); }}; doing this in JScheme is only 50% shorter: (let ((b (JButton. "Quit!"))) (.setBackground b (Color.red$)) (.addActionListener b (Listener. (lambda (e) (exit)))) ...) We've found that if you wrap a JScheme API around a Java API you can get real succinctness. For example, with the JLIB GUI library this becomes a one liner: (button red "Quit!" (Listener. (lambda (e) (exit)))) Having procedures that can take any number of arguments is a win. (button) takes its arguments and guesses where they should go, which has worked pretty well in practice. I use a similar style where the setter methods themselves become keywords in a constructor. This works for any object. So the trick is to look at a programming task, and come up with a JScheme style (minilanguage) for it. We've done GUI styles where Gui code can look pretty close to how the gui components are layed out. In Java, components are defined and then wired together in an apparently haphazard fashion. Macro's can be a big win from minilanguages, of course, since i'm talking to a master: http://www.paulgraham.com/onlisp.html Here's an example of shell scripting, basically from ideas from Olin Shivers: (define (directories dir) (map* File. (inputReader (run (cmd find ,dir -type d) (cmd grep -v CVS))))) This defines a procedure that takes a directory, dir and returns a list of all the directories under it. (run) is a macro that pipes shell commands together. There are two commands (cmd) one that finds the directories below dir, and one that filters out the CVS directories. The ",dir" is there because (run) has an implicit quasiquote (thanks to Olin). (run) returns a Process and the second line of the procedure uses map* and inputReader which are generic functions, as in Common Lisp. Together they return a list of Files from the output of the (run). Here's another example of generating JavaDoc for your application: (define-command (-javadoc) "Generate API documentation." (out (run (cmd (javadoc -private -author -version -use -windowtitle ,{[appName] API} -sourcepath ,srcDir -classpath ,classDir -doctitle ,appName -d ,apiDir ,packages))))) (define-command) is another macro for defining top level commands so i would typically invoke this from a shell as: bin/make -javac The {[appName] API} is "quasi-string" an alternative syntax for string-append that in this case would be equivalent to (string-append appNam " API"). This syntax is great for doing things like generating web pages. Olin's insight of switching in and out between Scheme and minilanguages is extremely powerful. It may seem confusing to beginners, but Tim has taught this stuff (well, at least the quasi-string stuff) to nonprogrammers with success. Succinctness becomes astronomical with the right minilanguage. For example, i need to read latitude and longitudes in formats from all over the world for one application. I currently have 30 different regular expressions for the different types. I have a macro that handles each type with only 3 lines of code: (disjunctionMatcher ("([NSEW])(\\d+) ?(\\d+).(\\d+).(\\d+)" ; Regex pattern. (dir deg min sleft sright) ; Names of pieces. (makeDMSDir deg min (makeFloat sleft sright) dir)) ; The result. ... Now that i think about it, if i used generic names, like $1, $2, ... there would only be two lines per disjunction. The 91 line macro use expands into 320 lines, which is nice, but not suprising. What is surprising is imaging writing and maintaining the equivalent of the macroexpansion in Java. The 30 disjunctive regular expressions produce one regular expression that is 1169 characters long, containing 115 fragments (an expression that matches something of value, like (\\d+), which will become part of a result in the 3rd line). To make a change, say to the 17'th disjunction, you'd need find the 17th (maybe 16th?) "|" in the regular expression and find the 17th if statement in the code. What makes it worse squared!, is that if you add or remove a fragment, you need to increment or decrement each line of code that tests or extracts a fragment after where you are. What a pain! Not sure how i'd do this in Java. Maybe just change the macro to generate Java for me. Constructing and iterating over objects needs to be succinct. So, when i actually have to write Java, i find it annoying. In JScheme, i've been playing with some ideas from Arc, like variations on (lt) and (if) and alternatives for lambda, which seem pretty nice. k >--Ken Anderson wrote: >> Another point about Chapter 11. - flexibility vs speed. >> >> JScheme is so flexible compared to Java that i write code in it as often as i can. We have one project that is almost entirely JScheme. Dispite the fact that JScheme is probably 100 time slower than compiled (no declarations) Common Lisp, the performance is still extremely good. We commonly grind over 100,000 or more objects at a time. We have moved a hairy regular expression matcher into Java, for speed, but still use JScheme to control interaction with it. In such an environment, where JScheme is a thin control layer on top of Java objects the performance reduction due to JScheme is typically 2x or 3x at the most. Quite reasonable cost for an interactive development environment. >> >> In Common Lisp, of course, we could have stayed in the language using the profiler to guide our optimizations. >> >> I think this flexiblity is what makes langauges like Python and Ruby so interesting to C people. As larger programs get written, such as Zope in Python, performance becomes more of an issue and and people focus more on compilers. JScheme has a compiler, but compiled code is no faster than interpeted. While a better compiler would be useful its never gotten to be a high priority. >> >> k >> |