Hacker News new | past | comments | ask | show | jobs | submit login
LFMs and LFSPs (Michael Vanier via PG) (paulgraham.com)
28 points by aston on July 9, 2007 | hide | past | favorite | 34 comments



I'd forgotten how well written that was. It's better than 99% of the essays on the web. And yet it was just an ordinary email, not intended for publication.


To get wider use of LFSPs, it would be important for more people to learn that you can and it's completely reasonable to interface several programming languages. You need to be comfortable with both languages to work on the interface, but I find it surprising we didn't practice this at school at all.

To explicate what the article touches: Perhaps you really have to use Java because of platform or workforce requirements, but it doesn't mean you have to use 100% Java. More experienced developers can build some of the low-level classes using C++ via JNI. They can also set up BeanShell or Jython for user interface and test scripters.


Unfortunately, interfacing multiple languages is itself not a strategy for LFM-people, because most of them can (or believe they can) only fit one language into their brains. There are costs to using more than one language on a project. Managers frequently overestimate these costs, because they're the ones who will look bad if their superior (or investor) looks at their development process and say "Wow, this is chaos. No wonder you can't ship software."

There does seem to be a loophole: you can use multiple languages if they all start with the same buzzword. So, the same manager who balks at a Python/C++/SQL solution because it uses too many languages has no problem requiring that his developers learn Java, JavaScript, Java Server Faces, Java Persistence Query Language, Java Expression Language, and the Java Standard Tag Library, because they all start with "Java". Same goes with XML/XSLT/XSLFO/XPath and derivatives.

Based on this observation, I suggest a renaming of all common hacker tools. Python should be renamed "ParrotSnake", Ruby "ParrotGems", Lisp "ParrotParens", SQL "ParrotQueries", JavaScript "Parrot in a Browser", HTML "ParrotTags", Haskell "ParrotMonads", and so on. Then we could freely use any of these on a project, because they're all the same language, duh. Of course, the Parrot project itself should be canceled, because it'd defeat the purpose to have them actually running on a common VM. This is strictly a marketing initiative.


Insightful! But does this in your opinion apply only to LFM-people - my understanding is most LFSP-people don't see multiple languages as an option either?


I dunno - most LFSP-people I know use multiple languages regularly, learn new ones as a hobby, and their "preferred" LFSP is just the top of a long list of skills. That's why we call them "smart people". ;-) Take a look at Programming Reddit - most of the Haskell evangelists are also quite fluent in Python and C and usually know Lisp, Ocaml, etc. too.

There do seem to be exceptions - Common Lisp in particular seems to be a LFSP where many of its leading practitioners only know Lisp. Smalltalk, too, but to a lesser extent since all the commercial Smalltalk jobs morphed into Objective C or Java jobs. But I don't really see Lisp as being at the top of the blub hierarchy any more - there's been 20 years of progress since then and some really interesting developments.


"Common Lisp in particular seems to be a LFSP where many of its leading practitioners only know Lisp."

The hardcorest Lisp hacker I know wrote the chapter on C++ in the UNIX Haters Handbook. He also wrote a C to Lisp compiler for the Symbolics Lisp Machines. (And he works as a C++ programmer right now. :-))

UNIX Haters Handbook, published in the early 90s: http://research.microsoft.com/~daniel/uhh-download.html


I'm not so sure. Most of the Common Lisp people I've met know Java. I can't think of a single one who doesn't know C.

Or did you mean Common Lisp people tend to have only that language as their LFSP, no matter how many LFMs they know? In that case I would agree - I definitely see CLers ignoring Ocaml, smalltalk, etc. For my part I guess it's because CL took a long time to learn fully and I need to build up some energy before I invest that amount of time in another LFSP.


I'm a game developer. At work, we use C++, C# and our own scripting language. Our language is embedded, so the communication protocols between C++ <-> our language are straightforward, and C++ <-> C# happens through function callbacks.

At home, I use C++ plus Lua. I tried to use Lisp, but the CFFI had problems interfacing with certain foreign libraries.

To get around C++'s constraints, my solution is to move all of my game engine code to Lua with only a lightweight wrapper in C++. Lua is extremely powerful - for example, functions are first class objects, so it has closures. It's dynamically typed, so I'll spend less time convincing a compiler I should be able to run my code. It's very fast, because it has a JIT compiler which uses DynASM to generate assembly code, so it's about 1/10th the speed of C - huge for a scripting language. But most importantly, the communication protocols between Lua and C are extremely clearly defined, and work. Communication between Lisp and C is much more vague, and only sometimes works - Calling Lisp from C, for example, don't work on all platforms.

So I'd guess what influences me in my choice of multiple languages is how well two languages can communicate with one another. Can all constructs from Blub translate into Floob? How does Blub call into Floob, and vice-versa? Is it supported on all platforms? Is there a lot of example code?

You can certainly have a language mashup and solve problems in elegant ways, but it all hinges on how elegant the communication is.

If you use several different languages, and if they don't share resources (Window handles and such), and if communication between them is not speed-critical, then you can write a networking layer which each language uses to communicate with every other language. But that option seems pretty rare.

Shawn


I work in a Java shop but do some statistical work using R. For me, Java is the easiest way to gather together data for an analysis. R is the clear winner over Java for doing statistical modeling. Gluing them together is possible, and some people have written bindings. However, these are not well documented, and they are not mature enough to use in a production environment. The difference when mixing together use of (J/X)* languages is that they have mature bindings (often simply because they are all written in Java).

I'm all for using the right tool for the job, but it's really hard to take on the risk of an integration one might have to maintain or expand upon. Using two languages isn't itself a problem, either for developers or managers.


Perfection is achieved, not when there is nothing more to add, but when there is nothing left to take away.

- Antoine de Saint-Exupery


I totally understand people's complaints about C++. However I am never sure if they are aware of some libraries that may concerns:

boost::function allows you to easily interchange function pointers, memeber function pointers and function objects.

boost::lamda allows you to define transient functions at the call site. This makes the old STL way more useful. To be sure the syntax can be a bit funky...

boost::bind pulls all the fun of boost::lamda and boost::function together.

With only the tiniest bit of discipline boost::shared_ptr's mean you never have to worry about memory management again.

boost::any and QVariants (from the Qt Libraries) help you manage dynamic typing. So an array of boost::any's can hold int's, char's, string's and AnyTypeYouWant's all at the same time.

Everything I've mentioned is either in the standard or will be soon. Really undergrad C++ is only the beginning. The STL and boost are almost a language unto themseleves. I never use raw arrays or naked pointers anymore and it's made development WAY faster while maintaining performance.

PG asked, why use a language where adaptors have to be built to get the functionality of lisp rather than lisp itself (or something like that.) I see his point but if the effort of getting lisp to work with C/C++ based code is greater than the pain of using an LFM, why not make the best of an LFM?

Has anyone had the same experience? Or am I putting the "ass" in masses?


for (i = 0; i < N; i++)

Odd example to use. It's always annoying to re-type the same thing over and over. But I recently went through a mountain of my Java code and replaced:

for (Object obj : list)

with

int size=list.size(); for (int index=0;index<size;index++)

I'm definitely very long suffering! It took a few hours but what you get with abstractions is a loss of control over implementation. When you use the tighter loop, the compiler turns everything (roughly) into this:

Iterator it=list.iterator(); //Which calls new ListItr(); for (it.hasNext()) { Object next=it.next();

The abstraction has to do that because it doesn't know whether the list (really, the java.util.Collection) is a LinkedList or an ArrayList or a Set. If your loop itself is in a tight loop, it isn't hard to call it millions and millions of times during program execution--which makes millions and millions of unnecessary allocations and method calls. There is a loss of performance which, at the end of the day, is more important than how much you enjoy your job.

Of course, other languages would allow you to specify an implementation for a given abstraction.


The replacements were for performance reasons, and it took a few hours to do. Sounds like it might been more productive to replace only the occurrences the profiler told you were causing problems...


Sounds like a case for macros.


A higher-order method 'iterate' on containers would also do, and it would be more idiomatic in the languages i know.


I don't understand: if you are sure that it is an ArrayList, why don't you use ArrayList instead of List to begin with? If you are not sure that it is an ArrayList, then you shouldn't use the for(int i = 0...) loop, because accessing each list element in a LinkedList by index would be extremely slow.

As for typing, in Eclipse you just type "for"+CTRL+Space and it completes the for loop for you.


For most of the article, I kept thinking, why hasn't he tried SWIG (http://www.swig.org). Keep the needs-to-be-fast stuff in C/C++ (hopefully with a well designed API), and code the fun, flexible, interactive stuff in the LFSP of your choice (sorry apparently not all LFSP supported).


I'd be careful with pure research languages like ocaml. It has some advanced features (polymorphic variants, functors, camlp4, etc. ) that even smart people have a hard time understanding, because they are probably the subject of someone's phd thesis. Also, LFSP's can be quirky: in Ocaml, for instance, native integers are only 31 bits (using 32 bits requires the Int32 module, which requires its own set of operations to use. Don't you love static type checking?)

Bugs in LFSP compilers probably appear much more often compared to LFM compilers.


Do we admit that smart people aren't infinitely smart? With LFSPs, I often get the feeling I'm too stupid for a language, but in many ways it's better than often feeling the language is too stupid for me.

Who cares whether you got 31 or 32 bits? The different operator names are because Ocaml doesn't have operator overloading. For statically type-checked and "overloaded" operators and functions, see type classes in Haskell.


I think Joe Average and Joe Boss's perceptions of the advantages of LFMs are completely bogus. The same solution implemented in an LFM and an LFSP will be more readable in the LFSP.


One of the features of LFSP's is that the code density is higher, which I'd argue is a good way to make the code harder to read, not easier.

Here's an example in ruby:

 (1...10).each{ |x| print x }
Tell me, does the 10 print or not? You'll need a second glance plus a good memory to answer that question.

Here's the same code in C++:

 for (int x = 1; x < 10; x++)
   cout << x;
Verbosity makes it easier for the non-you programmer to look at a random piece of code and decipher it. You feel smarter for having written that Ruby code (and the C++ is boring, I'll agree). But I'm gonna have to say the Ruby code has less readability outright just because it's so dense.


If (1...10) doesn't yield a list of numbers from 1 to 10 inclusive, that's a bug in Ruby. The problem is not density. It's perfectly clear what this code example should mean.


Sadly, no:

 irb(main):001:0> (1..10).max
 => 10
 irb(main):002:0> (1...10).max
 => 9
Ruby suffers from a bit of Perl-syndrome, and relies too much on magic syntax and "conventions" created by library authors. It's a nice language and all, but I still prefer Python over Ruby since Python is a simple, clean and elegant language with just a small number of elegant concepts.


Python does the same thing with range:

 >>> range(1,10)
 [1, 2, 3, 4, 5, 6, 7, 8, 9]
Leaving out the starting value starts at zero:

 >>> range(10)
 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Which is like dotimes:

 [1]> (dotimes (i 10) (format t "~d" i))
 0123456789
 NIL

There's an implied "less than the supplied high value" in all cases.


I'm not claiming that that example is dense code in and of itself, merely that encoding important differences in subtle ways is a by product of the LSFP way of thought. Verbose code is the opposite of clever code, so far as I can see.


Ruby and C++ both have a complex syntax, nevermind that being superficial. The LFSP example in the article was Ocaml, which doesn't even have operator overloading! What makes Ocaml an LFSP - the little I know about it - includes a nice combination of FP and OO plus a powerful type system.


The only reason I can think of why someone would look at the Ruby code and not be immediately sure that the 10 prints, is if they've become very accustomed to for loops like the one in the C++ example. It's conditioning, not intelligence, that makes the difference.


I think you missed part of my point, which is that "..." vs. ".." is a subtlety introduced by the mantra of high-density syntax seen in LFSP design.

The 10 doesn't print, btw.


I don't think the ".." vs "..." level of density is universal in LFSP design. Macros in Lisp/Scheme are words. See the sql-repeat macro with group-beginning? and group-ending? for example. It makes things more transparent than your typical LFM implementation would:

http://brl.codesimply.net/brl_4.html#SEC31


Then by your measure, is Perl the most-LFSP language out there? I think Perl reads like line noise. High-density syntax with clear, simple concepts is a desirable feature in a language. Assigning some function to every possible permutation of symbols (a la Perl) is just stupid. Ruby falls into the "just stupid" bin in this case.


I agree, the for loop is not intuitive at all, just historically present. However, try comparing the ruby against:

for x in range(10): print x


Actually Vanier may have a point that LFMs make programmers more interchangeable at big cos. So ultimately the argument against them may be startups.


Having a language that is massively popular is one factor in making it easier to find someone to take over code, but I think many managers have an unrealistic estimate of how much it helps. In some ways it actually hinders, by putting more noise into the hiring process.

In my day job, the ultimate argument was hiring a new graduate who was writing good production code in the LFSP on his second day. An LFSP lets you create a language that fits the problem domain nicely. This works both in normal companies and in startups.

If you're saying that startups can prove the value of an LFSP by being successful with it, I agree. If you're saying that doing a project as a startup eliminates the risk of having code that is hard for someone else to maintain, then I disagree. There are plenty of reasons why a startup might need to find someone capable of working within their codebase.


I think this is why all TRUE wizards write in Assembly Language.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: