My Language Is More Agile Than Yours: A Study of Arc
docs.google.com
docs.google.com
>> printIfNotNull(foo.getBar().getName()); // pointless method
>> in arc ...
>> ... aif is a macro that expands this code to
>>
>>(let it ((foo 'bar) 'name)
>> (if it
>> (print it)))
Why is a macro not pointless, while a method/function apparently is? I don't think that makes sense personally. Surely a macro is just a function that's been inlined - which may be better, depending on if you're optimizing for speed or size. Anyone explain?Also, I'm not sure if this was being serious or not:
>> Priorities in language design
>> A Language Should ...
If it was serious, it completely depends on what problem you're trying to solve as to what priorities you're going to have. ifNotNull(foo.getBar().getName(), function (it) { print it; });
but "function (it)" and all of the "().{}" are still blub. As PG points out on the arclanguage forums [1], an even simpler way of putting it is: (only.print foo!bar!name)
Which, without the Arc-specific syntax sugar, is: ((only print) ((foo 'bar) 'name))
So "only" is a modifier that makes a verb (function) happen only if its argument is truthy (think Python decorators).Note to PG: the dot operator is undocumented, and f.g.h.3 does not at all what I expect - looks like it does (f g h 3), whereas I expected (f (g (h 3))), in the spirit of Python decorators.
ifNotNull(foo.getBar().getName(), printIt);
Granted, in java passing functions around is more of a pain.
The strength and danger of macros is in allowing you to create new syntax, skipping all the extra blub that made your code conform (consistently) to the original parser. What I'd like to know though is how much humans depend on that blub.
It's one thing to write your own macros and use them consistently, or even for a group of people to come up with macros and use them consistently. But if people work independently, what happens when you read someone else's code and find they've come up with macros to do the same things as yours do, but with different names and different usage conventions?
Wouldn't that be a bit like having every scientist invent their own jargon?
Either humans can learn to translate that kind of thing on the fly, or they can't, and lisp with heavy macro usage will only ever be useful as a solo (or small group) language.
I don't know about "all scientists", but mathematicians do precisely this all the time! Each paper and each textbook begins by defining what symbols mean what, and which terms are used how. But a textbook is about as big a consistent block of terminology as you'll be able to find. In a grander sense, terms are constantly re-purposed and evolving over time [1].
Something as simple as "⊂" will mean "subset" in one book, and "strict subset" in another. In my experience, the older the book, the more divergent it'll be from current usage. This suggests that the language of mathematics evolves over time, precisely because every practitioner can change the language and push for her own terminology. There is no "Mathematics corporation" which creates languages people would be forced to choose from - no mathematician, no matter how great, would suggest that he would know what the "eternally right way" of expressing mathematics is.
This is especially apparent in more exploratory parts, where definitions start being named after their authors. I, personally, look forward to a time when I get to choose between a Norvig-garbage-collection and Graham-garbage-collection for each object in my code.
We're in this ridiculous stage of software engineering, where, if you're using Java, you have to do garbage collection The Java Way (you get to set a couple of settings, but that's it). No mathematician would ever be forced to make the decisions that Sun's engineers were forced to make when designing Java, because nobody could make those decisions correctly - how programs must interact with the call stack, the mechanics of method invocation, etc.
1. "Compactification" comes to mind as an example: http://en.wikipedia.org/wiki/Compactification_%28mathematics...
You can replace the word "macros" with "functions" and your point would be the same. Consistency in naming and usage is part of good programming, but the point is orthogonal to macros.
Edit: actually, it's not orthogonal: macros make it easier to be consistent because they let you write less code, so there's less code to contradict itself.
The vast majority of Lisp macros don't create new syntax anyway. They integrate seamlessly into the s-expression format. That way they blend in nicely with the rest of a program and it's easy to write other macros on top of them. An experienced Lisp programmer would only introduce new syntax when there is a big win in clarity - big enough to justify breaking with the surrounding structure and certainly big enough to make the above objection pointless.
By the way, one of the real problems with Lisp macros is that it can be hard to tell a macro call apart from a function call. (Various techniques have been developed to deal with this, such as indentation and naming conventions.) If macros were so syntactically convoluting this would hardly be an issue.
Also, it's worthwhile to remember that aif does a bit more - it's semantically the equivalent of cond, which sometimes eliminates a ton of if/else blub.
New in 1.8 you can omit the return and curly braces. So at least a little improvement!
(def only (f)
(fn args (if (car args) (apply f args))))
The . and ! operators got redefined so that x.y.z is ((x y) z) instead of (x y z). I think I posted the source on arclanguage.org.As novel as this may seem, most people who regularly program in languages other than Lisp also consider excessively verbose code to be a bad thing. (Hence the interest in refactoring.) If you're dealing with someone who just started programming a few months ago, they don't have enough experience to know why it's problematic, but then, they probably need someone to be patient and helpful, rather than smug.
You can say, "There are some things that are inherently hard to express in some languages that come naturally in others, and until you have experience with a couple different styles of languages, you'll only really know techniques used in your main language. Learning new languages adds new techniques to your problem solving toolkit. It takes a while, but it'll make you a much better programmer in the long run.", and it doesn't make you sound like you're sneering inside.
If you want to make the point that the way their language's object framework is designed makes any nontrivial program in it have a bunch of repetitive boilerplate, say that. Name-calling makes people defensive, not likely to consider new ideas.
"You're writing in 'blub', therefore you're a 'blub' programmer, and all you're capable of understanding is 'blub'. Maybe some day you'll wise up and use my 'language for smart people' (LFSP)."
Remember, "Blub" comes from the Blub Paradox (http://paulgraham.com/avg.html) in which one paradoxically believes that his or her language of choice (let's call it Blub) is the overall best language and that others are no good, usually due to missing some particular feature, or that they are too complicated.
An ironic example is that Paul Graham, who I believe is the creator of said paradox, asks rhetorically "If LISP is so great, why doesn't everybody use it?" and later, "I will say that LISP is at the top of the power spectrum." In effect, he's guilty of falling into his own paradox by assuming his language is at the top of the power spectrum, which is no different from assuming some other hypothetical language (that may have more capability than LISP) is too complicated.
But reading your post closely, you're saying that blub strictly only refers to people who believe their language is the best and others are no good. In the avg.html article, it also says "How can you get anything done in Blub? It doesn't even have y." and talks about people looking down on less powerful languages. Therefore, a person programming in Java (or assembly or whatever) isn't a blub programmer unless he believes that language is the best, that others are no good, and he looks down on less powerful languages (maybe a more accurate word for this attitude is "snob".) My experience is that most programmers don't do that.
The blub paradox comes from someone who only knows Pascal doesn't understand why you'd want classes or functional programming. (We're assuming he only knows classical Pascal, not Delphi.) It accounts for the resistance of C hackers to object oriented languages (as illustrated, rather passive-aggressively, in such books as Object Oriented Programming in ANSI C), the resistance of Java coders to first class functions (we have a design pattern for that! it's called "functor"!) and so forth. It doesn't apply to someone who knows Haskell criticizing Java for OO fetishism, or a Java programmer scoffing at Pascal for not having classes.
And the only place where I've ever read pg as critical of another language's abstractions is his remarks on Prolog. Now, that might be because he's fallen into the blub paradox, or maybe he has a good enough understanding of Prolog to authoritatively say it's only useful for 2% of problems and gets in your way the rest of the time. I can't read minds.
I only know the basics of Prolog from PAIP and CTM (one of many things I hope to get into deeper over the summer...), but it seems like it would be incredibly useful, albeit within a very specific niche. I wouldn't try to write an OS in make, but that doesn't mean it isn't handy sometimes.
I've always seen Prolog billed as a general purpose language. If it's intended as a special purpose language, then it doesn't even make sense to criticize it compared to Lisp or C.
That might be how some people use it... heck that might even have been the original intention, but I'd still rather just use it to describe the extra code a language makes you write.
If you think about it, there is no name for that... you could call it "cruft", but that's usually used to describe unnecessary verbosity in a framework or library, not the language itself.
http://en.wikipedia.org/wiki/Boilerplate_%28text%29
See also: Simon Peyton-Jones's "Scrap Your Boilerplate" papers (http://research.microsoft.com/en-us/um/people/simonpj/papers...)
x = (a = 7 * (b = 14)) + (c = 9 * ((d > 7)? e:f));
Now toss in some casts and the line becomes harder to read, but at the same time it's considered poor from to have 4 assignments on one line. Yet that's valid C, C++, Java, C# etc and it's got fewer symbols so it must be better according to the arc view of the world.
So is reqiring one assignment per line cruft or a good idea? How about those assignments? I would argue that one of the most useful mesures of a language is how easy it is to read other peoples code, but then you end up with something like Pascal...
PS: I acutaly like mantaining pascal code more than most languages then again it could have more to do with the experence of the people who use Pascal.
So token count is still not really what we want to measure even if it's better than line count.
As several lines or several statements?
x =
____(a = 7 *
________(b = 14))
____+
____(c = 9 *
________((d > 7)? e:f));
Same statement, same token count, multiple lines.
c = 9 * ((d > 7)?e:f);
x = ( a = 7 * (b = 14)) + c;
IMO is not that bad, it's the mixing of all the assignments and the conditional that makes it hard to parse, and c = 9 * ((d > 7)?e:f);
x = c + (a = 7 * (b = 14));
is fairly readable even if some of the ()'s are not needed.PS: ;'s I thought it was clear that the ;'s denoted lines not the whitespace which is meaningless.
I just wanted to say that personaly I have tryed learn to ignore indention and other clues becasue it's far more costly to misunderstand what the code is doing than try and use hint's that may have become old over time. To paraphrase: in that way lay bugs that look like dragons.
I also tend to do the same thing with comments on the first pass though. It's only when I don't understand the code or or it looks "odd" that I start to notice the extras like that.
I just wanted to say...
Edit: NM, reply is now working so I moved the comment.
Besides, I don't think anybody is really expecting you to cram as much into one line as possible.
It's not a linear continuum, with some "most powerful language" at the top. Different styles of languages have different strong points and weak points.
I, for one, find uniqueness types to be an easier to understand and use solution to the problem of maintaining referential transparency in the face of a stateful universe.
"Boilerplate" has that meaning, but without the connotation that the person using it is automatically a moron. (http://en.wikipedia.org/wiki/Boilerplate_%28text%29)
See also: Simon Peyton-Jones's "Scrap Your Boilerplate" papers (http://research.microsoft.com/en-us/um/people/simonpj/papers...)
Also in modern Java (and Ruby) projects you can typically write:
logger.debug("This is a stupidly expensive calculation: " + stupidlyExpensiveCalculation())
and it will get executed regardless and logged only if your logging settings, which are configured elsewhere and can be toggled with a mouseclick, are set to log the debug level.
Now, don't get me wrong, Arc's solution of defining a macro which checks to see whether your debug setting, which is defined elsewhere and can be toggled with a mouseclick, is on or not is a good idea. Its just not a revolutionary new idea for us wizened Big Freaking Enterprise App Ugly Language Programmers. Nor was it really that new in the halycon days of yore when Log4j crawled out of the primordial ooze.
isDebugEnabled() method is used to scare people since 2001, probably around the time when it came out of common use.
Macro that is being used for exactly that is not a distinguishing feature of the language. Java proponents may come out with, say, accessing array in constant time (how many car/cdr pairs you need for that?)
Important is that neither of the described facts add something to the value of one language against another and usually serve only as a basis for speculation.
Edit: when I'm saying "went out of common use", I mean that elegant alternatives like
logger.log("Expensive calculation is going to be: {0}", objectWithExpensiveToStringMethod);
have emerged.
Seems to me you say this like it's a good thing. Or I am misunderstanding...
In this case, that just slows things down. Worse yet is when it is stupidCalculationWithSideEffects(). And I have seen that before. :(
logger.debug {
...
}
Which can be done trivially in a language with convenient blocks/lambdas. Then the debugging expressions will not run at all with debugging disabled.Yes, having a meta-syntax as your syntax is very useful.
When I develop software, most of the big delays, and excuses for procrastination are in forcing my brain into action over the higher level concepts. I find a lot of the extra lines of code required when using BLUB can be written at high speed, with little "hurt" to the brain.
I'm not saying BLUB is just as fast to develop with, I'm just saying the benefit isn't as good as number of lines ratio good.
But I think during the writing of the program, you type the code, review the code, look at some other code, come back to that code, so you are writing/editing/modifying a particular piece of code multiple times.
I have a superstition that there is an element of polynomial effect on the size of the resulting program on the time to get it working or to parse code written by another person.
One of the more complicated features I waited to implement in the web version, just because I found it rather difficult to think of the problem in VB. The javascript code was just easier to write and think in, and once implemented I could translate it to VB.
Although I do not know if there is a limit to the benefits of moving to a smaller language. Also, this is just my personal experience and I do not know how much it generalizes to other people.
"One way to design a language is to just write down the program you'd
like to be able to write, regardless of whether there is a compiler that
can translate it or hardware that can run it."
Test driven really works, even for language development.Maybe it's indicative of my crowd and not the community at large, but even the smartest people seem to hit up Haskell-for-fun and Python-in-practice over Arc. It's hard to call them blub-programmers, since they're genuinely curious, hard-working people...
I am in exactly that camp, tho' I do fully intend to use Haskell for real once it becomes viable to do so. It's like the Python Paradox all over again.
Though classical Lisps all have funcall and apply (not sure about Arc).
makeOriginalScope := block(name := "originalScope"; block(writeln(name)))
block(name := "newScope"; makeOriginalScope call setScope(thisContext) call) call
//newScopeIn Io, the meanings of symbols are determined when the block is activated.
and
In my programming whenever I see myself writing blubber (I like that word), I just factor it out into a separate procedure and give it a descriptive name.
So when I'm reading the code of the calling routine, I rarely see blubber; it's contained in simple routines that are easy to read/verify.
Most of the examples here amount to thoughtful library development. With a good library, C++ and macro processor can also many of these things.
Here's a version of aif that works for most cases (I'm not claiming that C has the expressive power of Lisp):
#define aif(expr, val, code...) \
{ if ((expr) != (val)) { \
code ; \
} \
}
IMHO, this is good enough for blubber reduction. #define aif(expr, code) \
{ \
int it = (expr); \
if (it) \
{ \
code; \
} \
}
But this is still not close because in a Lisp, if is an expression that returns a value. We can't use the ternary operator because then there's no place to declare the "it" variable. You can't have an expression that declares a variable local to that expression in C no matter how hard you try.And the above lacks the "do {} while (0)" weirdness needed to make a cpp macro behave syntactically roughly like a function.
For example, it looks like Arc uses the "t" as a global true value. Is it really safe to assume "t" wouldn't be accidentally used as a local variable?
public void keyPressed(KeyEvent event) {
}
public void keyReleased(KeyEvent event) {
}
});
About Arc code: (on-key-press frame
'u (rotate-shape)
'j (drop-shape)
'h (move-shape -1)
'k (move-shape 1))
Is this code ADDS NEW key event handler OR OVERRIDES old one? When it is invoked - when key is PRESSED or when key is TYPED?here's a way where events are objects... rather than an invocation. Much more flexible.
a python + pygame example...
cu,
key_handlers= dict(u=rotate, j=drop, h=lambda : move(-1), k=lambda : move(-1))
buttons = {1:'u', 2:'j', 3:'h', 4:'k'}
for e in pygame.event.get(): if e.type == KEYDOWN: key_handlers[e.key]()
elif e.type == JOYBUTTONDOWN:
key_handlers[buttons[e.button]]()
elif e.type in [QUIT, MOUSEBUTTONDOWN, HTTPD]:
quit()The arc one is also less powerful, because it uses different function invocations for different types of events.
It's also likely more people will understand the python+pygame version. More people do currently understand the python version... hardly anyone uses Arc.
You need to understand non-common concepts. You also need to understand how to create functions - rather than just call functions.
Does that Arc one mean when the key down happens, or when a key up happens? Or maybe it means in between the key press and key down. Also, when will that code be called?
It's magic. Will it be called from a separate thread, or some other Magic time... before or after the screen is to be updated? What else is going on at that time in the program? With the Arc version you have no idea... it's not explicit.
Also Arc doesn't have first class events. It could have yes, but it doesn't.
ps. the python code above was mangled by the buggy Arc program running this forum.
Here is the code redone for simplicity without the extra shortness and power expressed in the other version.
.
for e in pygame.event.get():
if e.type == KEYDOWN:
if e.unicode == 'u':
shape.rotate()
elif e.unicode == 'j':
shape.drop()
elif e.unicode == 'h':
shape.move(-1)
elif e.unicode == 'k':
shape.move(1)
Or the declarative python version:.
dict( u=rotate , j=drop , h=lambda:move(-1) , k=lambda:move(-1))
Actually, that would make it more powerful. You can use polymorphism.
It's also likely more people will understand the python+pygame version. More people do currently understand the python version... hardly anyone uses Arc.
Missing the point. Conciseness is not best measured by eyes familiar with the language/library. It's best evaluated from the POV of the uninitiated. I have to parse and run a model to understand your first version. Your second version is as easily understandable as the Arc example in one way, but it's got a lot more pollution and an enforced higher line count.
I am answering as someone not well versed in either Python or Arc. (Actually a little more familiar with Python.)
There's the `printIfNotNull(foo.getBar().getName()); // pointless method`, also pointed out by axon, and the following code being duplicated instead of being a function:
if (log.isDebugEnabled()) {
log.debug("foo.valueOf(expression) = " +
foo.valueOf(expression));
}
BTW: heads up on your comment formatting: HN needs a blank line between text and code for 4-space indentation to be recognized.