LFMs and LFSPs (Michael Vanier via PG)
paulgraham.com
paulgraham.com
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.
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.
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.
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.
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
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'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.
- Antoine de Saint-Exupery
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?
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.
As for typing, in Eclipse you just type "for"+CTRL+Space and it completes the for loop for you.
Bugs in LFSP compilers probably appear much more often compared to LFM compilers.
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.
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.The 10 doesn't print, btw.
for x in range(10): print x
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. >>> 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.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.