Actually, you've presented Factor's hello world as though it were a file. They have a REPL, too -- they call it the listener, though. You can either use the GUI version it comes with or from the command line do
The syntax is aesthetically pleasing in an important way: it's very consistent. While postfix might not look "right" at first, it belies an incredibly simple parsing algorithm -- one that allows for easy definitions of the words like USE: and IN: and even :.
I presumed that without "USE: io" that "Hello World" was not
being sent to the standard output where as my arc/haskell examples actually were. i.e. With arc I could load/run a file/script with (pr "Hello World") and it would output "Hello World", but with Factor?...
I just went to each language site and looked for the Hello World examples. It may be that Factor could do a better job advertising AND that I need to spend more than 5 minutes looking :)
Yikes, editing race-conditions. I'll fork this off into a different reply.
I presumed that without "USE: io" that "Hello World" was not being sent to the standard output
Actually, that line's like an import statement in Haskell. I would say it's like load in Arc, but that's not really true; USE: and import have to do with module systems, which Arc doesn't have. If you're already familiar with modules, you can skip this stupid explanation, but...
Basically, modules let you structure your functions into different places so that they don't mess with each other. As a silly example, maybe you write a text adventure in Arc and name a function get, as in (get 'ye-flask). But Arc already defines a function called get, as in (map (get 'a) list-of-hash-tables). You want to be able to use both of them at the same time, but would rather not rename your new function. If Arc had modules, you could qualify the function name with the name of the module in which it's defined. Something like
But when you don't want to use Arc's get, you could still overwrite it with the text adventure's get and not need to prefix it with the module name.
This is a vast oversimplification, of course, but that's essentially what they do. So, in languages like Factor, all the I/O routines are in a module called "io". In the io library is a function called print. If you don't need to print things, you don't need to USE: io, which helps keep the "surface area" of your code small.
i.e. With arc I could load/run a file/script with (pr "Hello World") and it would output "Hello World", but with Factor?...
Because Arc doesn't separate anything into modules, you don't need to import things like pr since it's already there by default. The reason the Haskell code in my other reply could do without the import is that putStrLn is similarly defined in Haskell by default: it's in the so-called "standard prelude". That's what the Prelude> prompt tells you in GHCi. You can import other libraries in GHCi, and the prompt will tell you what you're using:
It may be that Factor could do a better job advertising
True. After all, transliterating the Factor example to Haskell looks something like
module Main where
import System.IO (putStrLn)
hello :: IO ()
hello = putStrLn "Hello world"
main = hello
Of course, in Haskell you would just write
module Main where
main = putStrLn "Hello world"
My point here is that the Factor code doesn't do any real magic. When you give it a chance, the syntax is quite powerful.
It's really neither here nor there: picking a language and getting at least somewhat comfortable with it is going to be better than endlessly deliberating. They all have their merits (even bad languages!).
Interesting idea, but I'm having some trouble seeing the consistency -- probably since I've never used PicoLisp. Please feel free to blind me with science, because my brain's not being a team player right now. (I suppose I could just, y'know, install PicoLisp, but asking dumb questions is much more fun.)
Arc has a handful of special forms (quote, quasiquote, if, fn,
assign), each there because they specifically have less-than-normal
evaluation rules (though at least quasiquote can be implemented as a macro:
http://arclanguage.org/item?id=9962). How does PicoLisp handle expressions
like the following (pseudo-)Arc code?
which makes sense if if is supposed to be a "normal" function. But this hardly seems useful, since the whole point of if is to conditionally evaluate one of its branches. So, my next thought is that perhaps it's the same as
(list (if t (pr "a"))
(if nil (pr "b"))) ; => prints "a" and returns (list "a" nil)
but why would I want if evaluating the arguments it's passed? It certainly shouldn't evaluate every argument, otherwise it'd be executing each branch (e.g., (if (signed treaty) (world-peace) (unleash-the-nukes))). So then, if this is the case, it's just evaluating some of its arguments? So if I wanted to return '(pr "a"), would I need to do something akin to the following?
(if something
''(pr "a"))
Or is the whole idea of "everything is a function, even special forms" just kind of a pun? How do you start dealing with the other cases, like assign and quote (and fn I suppose, which is just quote in PicoLisp)?
Is the idea just that it'd be cool to pass around special forms as first-class objects? This is a well-tread path, in some ways, since the topic of first-class macros comes up every now and then (http://www.google.com/search?q=site:arclanguage.org+first-cl...).
If anyone could enlighten me, I'd appreciate it. (Read: make mine brain am thinking goodly!)
because you've quoted the arguments. This is consistent; if you quote the arguments to if, you will get the same result (as you pointed out). Why would you expect a quoted argument to be evaluated?
The point is, just as you say, that you can pass functions which are normally considered 'special' and they will behave like any other function. Yeah, it's been done before (though you'll notice that picolisp comes up pretty often in your Google search; it's a canonical example) --- but that doesn't make it a bad idea; it's one less thing for the user to keep track of. I watched a person give up learning CL when they tried to pass a special form and discovered, after much frustration, that it was impossible.
This is consistent [...] Why would you expect a quoted argument to be evaluated?
I misspoke regarding consistency. What I meant was that the "useful" way (conditionally evaluating the branches) would be inconsistent, but the consistent way doesn't seem very useful (because the point of special forms is to behave specially).
Yeah, it's been done before [...] but that doesn't make it a bad idea
Certainly not! I didn't mean to imply first-class macros/special forms are a bad idea --- let alone just because they've been done before. I meant that the ideas are similar; since there's been more discussion about first-class macros, it might be useful to ponder those.
Even so, first-class special forms seem less promising than first-class macros. rntz already pointed out that functions can mimic PicoLisp's behavior for special forms, but you can even mimic the "useful" behavior for certain cases of if:
arc> (def iffn (x y z) (if (eval x) (eval y) (eval z)))
#<procedure: iffn>
arc> (iffn '(prn "test") '(prn "then") '(prn "else"))
test
then
"then"
arc> (iffn '(do1 nil (prn "test")) '(prn "then") '(prn "else"))
test
else
"else"
But that's still kind of a joke. And what about ones that don't even evaluate their arguments, like fn? If
(car:map if '(t) '((pr "a")))
corresponds to
(if 't '(pr "a"))
then does
(car:map fn '(t) '((pr "a")))
correspond to
(fn 't '(pr "a"))
which isn't even valid syntax? (At least by the way Arc's quote and fn interact; I understand PicoLisp's quote and fn are unified to just quote.)
I'm not against first-class special forms iff we can reason about them. It's just that reasonable interpretations look like solutions in search of the problem.
It's consistent, but it means that passing special forms to higher-order functions is essentially just a form of punnery - it lends you no more expressiveness. There is no way (unless I'm mistaken) to get (map if '(t nil) X), where X is some expression, to evaluate the first element of X (or its evaluation) but not the second. So I could as well just define a function:
(def iffn a
(iflet (c . a) a
(iflet (x . a) a
(if c x (apply iffn a))
c)))
(map iffn '(t nil) '(1 2) '(5 6))
=> (1 6)
At a loss for something useful to contribute (sorry), I thought it might help discussion to rewrite the code to be simpler -- people won't need to wrestle as much with the auxiliary definitions that way.
I know you probably meant to use things like merge-tables in the large, but in the example you only use it for a degenerate case that happens to be fill-table. I also assume all the unbound things in the macro are defined elsewhere (i.e., you define nav somewhere, param is bound (is it supposed to be arg?), req is gotten from macro-expansion context, etc.). So, I'm left with
Awesome comments, thanks! I'm new to lisp. Not reading about lisp, but actually hacking on it :)
Yes I was aware I was leaving nav, etc. unspecified, I was trying to evoke say the 'Goooogle' nav line in searches. I've been using 'req' purely as the arg to defops, which wasn't clear at all, sorry.
Thanks for the pointers to fill-table and arg. I just wasn't aware of them.
Excellent! I love projects that directly make programming more productive. Is this implemented solely in Java? How hard do you think it'd be to write a decent profiler in vanilla Arc (if only for the sake of portability)?
There was a vanilla arc profiler pushed to anarki a while ago - see http://arclanguage.org/item?id=5318 ... it requires you to specify which functions to profile. Which could be more or less useful, depending on what you need.
I'd guess that any kind of decent profiler has to get in under the hood somehow - so to get something similar working for vanilla arc you'd need to go hacking ac.scm, probably somewhere inside
It'd be interesting if there was a pattern to hyphenated names. I assume there isn't because I just hyphenate when it seems like I "should". i.e., Names are often broken up by hyphens because they're easier to read that way. No real algorithm for that. Things like the length of the name can play a factor (e.g., expand-metafn-call doesn't run together like expandmetafncall). Now that I think of it, that's probably why Common Lisp has so many hyphens: its long names would run together otherwise. Even so, it has butlast, notany, notevery, pushnew, and the p vs -p inconsistency.
Just from this, I see names that work well with hyphens: I can't parse createacct on first glance, hourssince & dayssince are awkward with the two S characters, responderr looks like "responder" with an extra R, pprprogn has too many similar letters jammed together, logfilename could be parsed as either "name of the logfile" or "log the filename", nof is just weird, etc. But there are names that don't strictly need hyphens: I think endtag, filltable, getuser, loadtable, loadtables, randkey, and others look fine, which indicates to me that the issue really is just aesthetic.
Versus the same run in GNU clisp with the Shootout's SBCL code:
> (time (spigot 1000))
[OUTPUT ELIDED]
Real time: 1.022299 sec.
Run time: 0.888055 sec.
Space: 104310256 Bytes
GC: 133, GC time: 0.420025 sec.
NIL
Their outputs were the same, so the Arc program seems to be working (to say nothing of its optimality; I basically did a straight port). This benchmark's fun if only because you get to watch pi being calculated before your eyes. Nifty algorithm.
This is an awesome reference, thanks. I'll be using a few of these. The pi calculation uncovered a rainbow weakness: rainbow relies on java "long" integers for 'int types; but these are 64-bit signed integers and the algorithm fails at the 5th digit. So I'll need to think of a way to incorporate arbitrary-precision maths before I can run this benchmark. One thing is certain: using java's BigXXX family of numeric types is going to hurt.
(= pi (* 4 (atan 1))
days-per-year* 365.24d0
solar-mass* (* 4d0 pi pi))
(def make-body (x y z vx vy vz mass)
(obj x x y y z z vx vx vy vy vz vz mass mass))
(= jupiter* (make-body 4.84143144246472090d0
-1.16032004402742839d0
-1.03622044471123109d-1
(* 1.66007664274403694d-3 days-per-year*)
(* 7.69901118419740425d-3 days-per-year*)
(* -6.90460016972063023d-5 days-per-year*)
(* 9.54791938424326609d-4 solar-mass*)))
(= saturn* (make-body 8.34336671824457987d0
4.12479856412430479d0
-4.03523417114321381d-1
(* -2.76742510726862411d-3 days-per-year*)
(* 4.99852801234917238d-3 days-per-year*)
(* 2.30417297573763929d-5 days-per-year*)
(* 2.85885980666130812d-4 solar-mass*)))
(= uranus* (make-body 1.28943695621391310d1
-1.51111514016986312d1
-2.23307578892655734d-1
(* 2.96460137564761618d-03 days-per-year*)
(* 2.37847173959480950d-03 days-per-year*)
(* -2.96589568540237556d-05 days-per-year*)
(* 4.36624404335156298d-05 solar-mass*)))
(= neptune* (make-body 1.53796971148509165d+01
-2.59193146099879641d+01
1.79258772950371181d-01
(* 2.68067772490389322d-03 days-per-year*)
(* 1.62824170038242295d-03 days-per-year*)
(* -9.51592254519715870d-05 days-per-year*)
(* 5.15138902046611451d-05 solar-mass*)))
(= sun* (make-body 0.0d0 0.0d0 0.0d0 0.0d0 0.0d0 0.0d0 solar-mass*))
(def apply-forces (a b dt)
(withs (dx (- (a 'x) (b 'x))
dy (- (a 'y) (b 'y))
dz (- (a 'z) (b 'z))
d (sqrt (+ (expt dx 2) (expt dy 2) (expt dz 2)))
mag (/ dt (expt d 3))
dxmag (* dx mag)
dymag (* dy mag)
dzmag (* dz mag))
(-- (a 'vx) (* dxmag (b 'mass)))
(-- (a 'vy) (* dymag (b 'mass)))
(-- (a 'vz) (* dzmag (b 'mass)))
(++ (b 'vx) (* dxmag (a 'mass)))
(++ (b 'vy) (* dymag (a 'mass)))
(++ (b 'vz) (* dzmag (a 'mass)))))
(def advance (solar-system dt)
(on a solar-system
(for i (+ index 1) (- (len solar-system) 1)
(apply-forces a (solar-system i) dt)))
(each b solar-system
(++ (b 'x) (* dt (b 'vx)))
(++ (b 'y) (* dt (b 'vy)))
(++ (b 'z) (* dt (b 'vz)))))
(def energy (solar-system)
(let e 0.0d0
(on a solar-system
(++ e (* 0.5d0
(a 'mass)
(+ (expt (a 'vx) 2)
(expt (a 'vy) 2)
(expt (a 'vz) 2))))
(for i (+ index 1) (- (len solar-system) 1)
(withs (b (solar-system i)
dx (- (a 'x) (b 'x))
dy (- (a 'y) (b 'y))
dz (- (a 'z) (b 'z))
d (sqrt (+ (expt dx 2) (expt dy 2) (expt dz 2))))
(-- e (/ (* (a 'mass) (b 'mass)) d)))))
e))
(def offset-momentum (solar-system)
(with (px 0.0d0
py 0.0d0
pz 0.0d0)
(each p solar-system
(++ px (* (p 'vx) (p 'mass)))
(++ py (* (p 'vy) (p 'mass)))
(++ pz (* (p 'vz) (p 'mass))))
(= ((car solar-system) 'vx) (/ (- px) solar-mass*)
((car solar-system) 'vy) (/ (- py) solar-mass*)
((car solar-system) 'vz) (/ (- pz) solar-mass*))))
(def nbody (n)
(let solar-system (list sun* jupiter* saturn* uranus* neptune*)
(offset-momentum solar-system)
(prn (energy solar-system))
(repeat n (advance solar-system 0.01d0))
(prn (energy solar-system))
nil))
I didn't have the patience to wait for Arc to finish the benchmark's n = 50,000,000 (gives you an idea of how well it performs!), but on smaller values it seems correct.
The reference implementation, run from a GNU clisp REPL (though I suppose I should use SBCL for a fairer time comparison, I'm just interested in the accuracy of the Arc results with this run):
> (time (nbody 50000))
-0.169075164
-0.169078071
Real time: 37.575706 sec.
Run time: 32.862053 sec.
Space: 374412048 Bytes
GC: 477, GC time: 1.836092 sec.
NIL
The way you get a REPL circa mzscheme 4 is to pass -i, but
-i, --repl : Run interactive read-eval-print loop; implies -v
-v, --version : Show version
and they changed the -m flag to mean
-m, --main : Call `main' with command-line arguments, print results
So, using -i will still display the banner, and there doesn't seem to be an option to mute it.
$ mzscheme -if as.scm
Welcome to MzScheme v4.1.5 [3m], Copyright (c) 2004-2009 PLT Scheme Inc.
Use (quit) to quit, (tl) to return here after an interrupt.
arc> ^Cuser break
=== context ===
~/arc/ac.scm:1141:4
> (tl)
Use (quit) to quit, (tl) to return here after an interrupt.
arc>