Why Lisp macros are cool, a Perl perspective (2005)
lists.warhead.org.uk
lists.warhead.org.uk
Would you mind listing a few of these?
I would add "Modern Perl" by chromatic: https://pragprog.com/titles/swperl/modern-perl-fourth-editio...
With lots of elegant examples, this book really shows that perl is not a write-only language.
Sure:
I already mentioned
Mark Jason Dominus: "Higher-Order Perl"
as it is a great introduction of functional programming and goes into all the gory details of how to implement lazy evaluation on top of closures.
Damian Conway: "Object Oriented Perl"
Comes to my mind as well. It's basically an introduction to object oriented programming so kind of similar in spirit to "Higher-Order Perl". It even talks about implementation techniques of OO protocols. Neat stuff.
These books are great even today as they transcend the Perl language.
A little outdated but still great to read are:
Randal L. Schwartz "Learning Perl"
Randal L. Schwartz "Intermediate Perl"
Brian D Foy "Mastering Perl" (my personal favorite in this series)
if you need to learn Perl 5 today.
I personally also liked
Tom Christiansen, Nathan Torkington: "Perl Cookbook"
which was basically just a large collection of programming solution recipes but usually also contained elaborate discussions on the various pros and cons of the corresponding solutions. I think it was in this book I first learned about the Fisher–Yates shuffle algorithm.
Brian D Foy: "Learning Perl 6" is a great book about Raku and is a more recent example of the great heritage of high quality introductory books about programming in the Perl domain.
The best one I've come across so far is from the guy that goes by "Ovid" (Curtis Poe). It had the right amount about the language, environment, package managers, popular libraries...etc. I liked the code as well.
Somewhat more specific, and many of the libraries used are dated. But it did provide you with a huge amount of information, from writing simple echo clients with raw sockets up to fully multi process HTTP deamons, in a very accessible way.
It’s a good way to learn from because in practice you have these messy problems you’ll sometimes have to tackle from various sides before you push for a solution. Also software development is not about knowing all algorithms by heart but knowing when to reach for established work that is bullet proof and will save you and your organization a lot of headache in the future.
Witty language, and just the right amount of complexity.
Although the number of pages is high, these books were produced with incredible attention to detail.
For the most recent editions of Learning Perl, Randal Schwartz has been joined by brian d foy and Tom Phoenix. I suspect that those editions are good, too, but I haven't checked them out.
It's instructive to compare Learning Perl, which was (I think) under 300 pages in its early editions, to Learning Python, which is nearly 1600 pages long in its fifth edition. There's a lot of value in the longer book---but these two books exemplify very different ideas about what an intro book should be.
A voice from a simpler time. Had me laughing.
mjd rocks but 2005 was before I made custom keywords in perl "work" on CPAN, and even further before people who were competent to do so obsoleted my horrific proof of concept hack with perl core features in 5.14/5.16 - which have since allowed many cool things such as http://p3rl.org/Future::AsyncAwait
Lisp macros are, of course, still cool, and this is why I got into this sort of hackery in the first place.
Anyway, when reading the code, you're safe in assuming "=3D" is just a single plain old equal sign. In the "quoted-printable" encoding scheme the two characters following an equal sign are the hex value of the intended character. In effect, equal signs need to be escaped using the "=3D" sequence.
This drove me a little nuts as I was reading and trying to code switch between the assorted languages used in the examples.
Quite sad btw that many lisp book are old. And wonder if those Perl book can they be rewritten at least just GitHub source code only for study purpose.
On a related note, https://www.youtube.com/watch?v=e1T7WbKox6s is very much worth watching.
Having written a skeletal macro system for a Smalltalk, I do have a lot of sympathy for the effort he had to go through.
On the other hand, Regex::Debugger is something I really miss when I am working in any other language. I think Perl is unique in having a real debugger for regexes. The closest analogue is re-builder in Emacs, which lets you type a regex and see how it matches against a bunch of sample text. Very useful, but not the same as stepping through the regex.
But, most people are happy with just typing and copy pasting stuff around. They don't care. I've shown them before and after macro refactoring code comparisons and they just stare blankly, they don't see the point.
They code stuff like this on a daily basis:
Var stuff() As Stuff
stuff.Add(New Stuff("foo"))
stuff.Add(New Stuff("bar"))
stuff.Add(New Stuff("baz"))
And they really don't have a problem with it :)I find it almost always better to write dumb code like this until I it does what it needs doing. It's much more straight forward, in a very literal sense, to find suitable abstraction and compression from there. DRY is about knowledge representation, not about repetition or boilerplate. Solving the former first in a good way lays the foundation for solving the latter, which can then be done with more powerful abstraction tools such as macros.
That's when I would feel the dumbest programming - undoing what was supposed to be me being smart.
I think I read somewhere that the worst code comes from the wrong abstractions.
> I think I read somewhere that the worst code comes from the wrong abstractions.
I'd say no, it comes from cut & paste :) seriously
Same! Then I abstract. But others never get past cut & paste.
> DRY is about knowledge representation, not about repetition or boilerplate
err, isn't it exactly about not repeating yourself? I mean, for the ultimate aim of representing the thing better, don't repeat it everywhere, ergo encapsulate?
"Every piece of knowledge must have a single, unambiguous, authoritative representation within a system"
To contrast: Data especially can be very repetitive, say you have a bunch of configurations of somewhat similar things, or you have records that represent purchases at a given time of a thing by someone, they will tend to look very similar, maybe even almost the same
DRY is not about reducing this kind of repetition, because this is just like it should be. Sure, you might want to generate/automate repetitive things or compress them into something more readable and so on, but that's not what DRY is about.
Ultimately it is a form of normalization and decoupling. Ideally you want your code to be in a state so when you change things there is as little coordination required as possible.
Yes!
Every piece of information that is in two or more places is wrong in at least one of them. That's as true of code as it is of data.
Then I would show them how to use a simple macro I had written for sql statements that would turn a block of statements into a transaction: (sql-tran (statements) (commit) (rollback))
I would point out that only the commit or rollback expressions would execute. It also generated symbols so that these could be nested.
At the end of the day, you get code that looks like:
(transfer-money amount from-account to-account)
and it really feels like cheating because it's all wrapped up in transactions and handles errors without having to see that splattered across the code.
Even as a beginner in Lisp I had more and easier code reuse than I've had in any language before or since.
DRY has trade-offs like everything else. Code that is very new, maybe built to satisfy business requirements that are still fuzzy, and shifting around a lot, can result in perfectly DRY code returning a net negative cost/benefit.
As your requirements solidify and the code matures, the net benefit of DRY code increases, often at a super-linear rate.
Going back to your example, I'd say go ahead and copy and paste if and when that strategy fits the scenario, and with the consciousness that you're taking on planned tech debt.
One thing that helped greatly with that is the “defer” statement. It lets you defer a section of code to the end of the current scope. Which means you now have compile time access to the entire current scope.
We’re writing a compiler for custom ML hardware using MLIR. MLIR has the concept of a current insertion point when you’re building an AST.
emacs users will know where I’m going with this.
We now have a save_excursion macro which stores the current cursor, then resets it at the end of the scope automatically. So when you’re building a loop or an if statement, you normally have to call (pseudocode):
make loop
set cursor to loop body
make statements inside the loop
set cursor to “after loop body”
Now it’s make loop
save_excursion();
make statements inside the loop
Notice that the work decreased by 25% (three lines instead of four). In C++ land especially, this is a big win.The “make loop” part is actually so long that our linter breaks it onto several lines. But after making a macro for it, it looks like:
affine_for(loop, idx, 0, n, 1);
make statements inside the loop
And the statements in the loop can of course reference “idx”, the iteration variable, or “loop” (a first class object representing a for loop).Boom, now the work is cut by 50%. Two lines of code. Technically it went from three to one, and it’s far more readable.
The usual lisp disciplinary rules apply. You need to be familiar with variable capture, and how to make variable names at compile time. (In C++ I use a UNIQ(x) macro, which gives me a unique name for x. You can capture it into a #define by making a second #define and passing UNIQ(x) into it. So in practice most macros like save_excursion become two #defines, since you have to reference the unique name of the variable to store the cursor.)
I don’t know if it’s common. I assume not. Most lispers are rightly allergic to C++. But most lispers also lose the benefits of working in a team setting — so in practice, porting as much benefit from lisp to other languages is the biggest win of all. And that requires you to learn lisp. (Interested readers should look up the Blub Paradox, then move on to studying On Lisp.)
Perl encourages idiomatic programming styles which make collaboration difficult. I'm the eng mgr of a team which writes (among other things) Perl for big data processing applications and none of our programmers understand code the others have written without extensive (measured in weeks rather than days) analysis. I've never encountered the problem to this extent on Java code bases.
I find that Test Driven Development helps to tackle that issue as you get a set of tests that show what the code should be doing and the Refactor step reminds the programmer to restructure the code so that it's easier to understand.