re

What Kind of Fundraiser Are You?

Fundraisers come in all varieties. From aggressive to docile, from passionate to indifferent, there are many ways to bring money into a non-profit or political campaign. Just what is the best method of fundraising? The best method is the one that brings in the most money for the cause. There is only one caveat; donors must be respected at all times.

While fundraising happens in call centers, fundraising isn't a typical call center job. Most call centers don't require the depth of experience that fundraiser call center jobs require. A fund raiser can be expected to have a solid grasp of political, social and environmental issues. A telephone fundraiser can spend the morning trying to elect certain politicians and the afternoon trying to protect natural resources  The best telefundraisers have flexible minds and are gifted speakers.

Passion helps a lot but is certainly not the only way to get a donor to contribute. Knowing the issues you're calling about, even if you aren't as passionate about them as others, goes a long way to building credibility with donors. Rapport building is just as important, Donors give to people they like, Get to know your donors and they'll respond,

The next tip is counter-intuitive  Ask high. Most callers, and especially new callers, think donors will be insulted by large requests. In fact high asks have the opposite effect, Many donors are actually flattered to receive a large request, In other cases donors find these request to be humorous given their personal financial situation. Humor builds rapport and rapport secures donations.

The best fundraiser to be is the fundraiser that is most effective with the donor that's currently on the line. Be versatile  The more fundraising styles in your portfolio, the more money you'll raise.

Thank you for reading this. Please leave your comments below and check back often for new posts all about the world of telephone fundraising.




re

Elements of a Great Fundraising Script.

Some call centers are very strict about fundraisers reading directly from a script. Other call centers advise fundraisers to stick to the script, but add additional details when the call requires these extras. Still other fundraising call centers allow fundraisers a great deal of freedom as long as the fundraiser stays within the general parameters of the fundraising campaign. Each strategy has its own merits, however there are some common elements that should be included in all fundraising calls.

Getting a foot in the door.

The first step in most calls is the greeting. Usually the fundraiser identifies their self and the group which they are calling on behalf of. This step should be gotten through quickly but not rushed. The realities of the campaign will dictate how the fundraiser proceeds with the introduction. In some cases the introduction can be delayed until after the a basic description of the organization and its funding need is made. Promise to be brief with your call and stick to this promise./

Expressing gratitude.

The next step is to thank the donor. Whether its a simple thank you for taking the call or a more elaborate thank you for past contributions and supporting the cause, this is an important step. Sincere and elaborate thank yous let the donor know that their help is appreciated. Thank yous also tend to extend the call; people rarely hang up on callers while the caller is praising their support and reaffirming their decision to support the cause. Additionally, the longer a donor stays on the phone, the more likely they are to make a contribution.

The reason for the call.

Next, quickly go into some of the current issues faced by the organization and what is being done about these issues. Don't skimp on the details but don't speak in a monotonous way either. Express some real interest in the cause. Listen for cues from the donor during this and all stages. If they agree with something you're saying; elaborate on the subject. Build rapport. Remember; men and women process information differently. Read other posts on this site to find out which language to use for each kind of donor. Alternatively, if the donor indicates that they're busy; acknowledge that. Repeat that you'll be brief or just get right into your first donation request.

Going for the donation.

The first ask. Given the reasons stated above make a solid ask for a minimum of 3 times highest past gift. Be assertive and let the donor respond. Don't laugh, don't whine. If the need is real, the request should be real as well. Defend your request if required to; don't just lower it. Defending the amount of the first ask gives instant credibility to the importance of the issue, In fact, state that the reason you're requesting a large donation is because of the serious nature of the issue, Only then begin to lower the amount that your'e requesting.

A second attempt.

The second ask. Quickly elaborate on the need. Acknowledge that the donor isn't able to give 3 times their highest past donation. Considering the need, ask for 2 times the past donation. Again defend your request. The more legitimate you sound, the more likely the donor is to give you money. For many fundraisers lowering ask amounts deteriorates into desperation. Although this is a negotiation the need is legitimates and as a fundraiser you want to get the highest possible donation,

One more try.

The third and, not necessarily, final ask, This is where the fundraiser asks the donor to meet the level of their last contribution. This is obviously the level that the donor has been comfortable giving at in the past. Again stress the need and elaborate on the potential consequences of not reaching an adequate level of funding for the campaign in question.

Taking no for an answer.

If stopping here, without securing a donation, take the time to sincerely thank the donor once more. This establishes that you, the fundraiser and the organization, respect the donor no matter what they can or cannot give at the moment. This also helps to reinforce an attitude of respect and gratitude which should be extended to all donors at all times.

The forth ask and so on.

Many times a donor who can't match a previous donation will express regret that they simply cant afford the same amount. If applicable, ask for an even lower amount down to the minimum level of donation that can be taken on a specific campaign. Remind the donor and yourself that every donation, no matter what size, counts. After all in most fundraising campaigns, the many small donations greatly out number the amount of money which is generated by the larger ones.

Get it on a credit card.

The credit card ask. Credit card donations fulfill instantly. There are no pledge cards to send out. Obviously securing a donation on a credit card is favorable to a mailed in pledge card. Credit cards on the phone are favorable to online donations as well; donors can easily be distracted and forget to make their donation.

Ease their mind.

Security is the main concern with credit card donations, Donors are rightfully fearful of identity theft. Every call center has methods in place to protect the credit card information of donors. Patiently explain these procedures as well as why credit card gifts are the best gifts that donors can make.

Be prepared to further explain  the value of credit card donations and their secure nature. Many donors will give by credit card once they have been properly assured of security measures. If not, follow your organization's standard pledge card procedure.

Wrapping up the call.

Again, take the time to sincerely thank the donor for their help. Answer any additional questions and then politely end the call. Following this method on every call improves dollars raised as well as the quality of each outbound call.




re

The Future of Fundraising is Local.

With the economy finally starting to gain some traction, few people are thinking about those whom society is le aving behind. These are the working poor, the sick and the elderly. As the wars in Iraq and Afghanistan wind down, we will see more soldiers and their families in need.

Sometimes fundraising calls for a national effort. The only way to gather money and resources quickly is by calling or mailing into every state for support. This is a proven method of soliciting money. This isn't the case for every fundraising effort however.

There are some issues that could just as well be taken care of locally. Additionally, some causes are actually better served by using local fundraisers. Local has a number of benefits; we'll name a few below.

More of the money goes to the cause.
Some national fundraising companies can take as much as 80% of what they raise for an organization. Millions of dollars are wasted in this way. Local groups just can't afford to pay these prices. A local campaign, whether staffed by volunteers or local professionals, ensures that more of the donated dollars go to work immediately.

No one knows local issues like local people.
Using locals to fund raise means having a team that understands the issue and is passionate about it. The more knowledgeable and interested in an issue a fundraiser is, the more likely they are to secure donations.

As a nation, a number of important issues are affecting Americans;
children going to bed hungry, seniors and veterans not getting the care that they deserve and homelessness are just a few of these issues. What all of these issues all have in common is that they begin and end locally.

We can choose to wait for a solution from Washington, but President Obama's job forces him to look at the big issues, not the small ones. National organizations face similar problems; they have the money and resources, but not the organization to implement relief locally. Local fundraisers raising and spending money locally have an immediate effect on communities.

Bringing the idea of raising money in the community and solving problems locally is one whose time has come. More local groups are starting to see the power of collecting donations at home. As the idea spreads, the benefits will only become greater.




re

Cognition, culture, … and communication?

An interesting recent review article (Wooster et al., "Animal cognition and culture mediate predator–prey interactions", Trends in Ecology & Evolution 2024) argues for bridging the academic silos of "predator-prey ecology" and "animal cognition and culture": Abstract: Predator–prey ecology and the study of animal cognition and culture have emerged as independent disciplines. Research combining these disciplines […]




re

Registration open for NOTOCON XIV

Registration is now open for National O.T.O. Conference XIV, to be held in Denver, Colorado, August 3-6 2023. NOTOCON is normally held every two years, but it was canceled in 2021 due to the pandemic, so this will be our first NOTOCON since 2019.




re

NOTOCON Hotel Registration Closing

NOTOCON XIV is fast approaching! The cutoff date to receive our group rate at the hotel is Friday, July 21st. Please reserve now. The speaker schedule has been published on the NOTOCON website. Event registration is still open! We can’t wait to see all of you in August, and wish you all safe travels!




re

USGL annual report for fiscal year 2022 published

The U.S. Grand Lodge O.T.O. annual report for fiscal year 2022 has been published. This and all previous annual reports can be found here.




re

A Failure Is Me

Terrible! I was thinking all day about how I needed to write a blog-post and how I'd surely have time after such-and-such meeting or after the 6:00pm concert, but I didn't get back home until after midnight. So this post is backdated in the most embarrassing of traditions. -1,000 points

Tonight we saw two great tastes that go great together: Alex G and Alvvays. I think both of these bands are great. Alvvays has been on my radar for many years, but they didn't quite click for me until their most recent album Blue Rev, which is probably my favorite disc this year (although it came out in 2022). It's definitely one of those albums that I like to listen end-to-end, but it also stars on the 5 minute playlist that I am using for my aforementioned project to run a 5m00s treadmill mile, so I listen to two tracks from it (Pharmacist and Velveteen) many times a week, often in pain. Alex G I discovered from the soundtrack to the indie film We're all Going to the World's Fair. I also think his music is great, and weird, but I'm still trying to wrap my head around how many people were at this pretty big concert venue enraptured by this kinda weird band. I think the simplest explanation is that "the kids are alright."

Anyway, now it is quite late and I am quite tired. I am still working on this math project and still "making progress" but wary that it's been in that state for quite a while now. But I do basically know how to finish projects or move onto other things!




re

Green scream

In a fashion that's thematically appropriate for the project, I'm "taking my time" with this video (e.g. I am still writing new code for it today??). It mostly means that I feel behind a lot. But I think I am truly close now to being done. I got all my new gear working together, 3D-printing rig pieces and so on. This has been generally fun. I'm also enjoying the occasion to experiment with new approaches and video editing techniques. I even cleaned out a significant section of my basement for a temporary studio:


Green scream


I think I have about 120 seconds of finished video here, which is far worse than my usual bad pace of about an hour a minute. Fortunately the rest should be much more straightforward, and I hope to just record the audio and be done with it this weekend. Pro tip, though: Don't install the new version of Adobe Premiere Pro while you're knee-deep in a complicated edit. Why would you press that button?

My procrastination: I fully beat Teardown and all the stakes in Balatro (but I may try to finish the last few challenges). Both good games, recommended. For light procrastination I have been playing Grapple Dog which has cute graphics and writing and is getting better as the levels get more challenging, but I probably wouldn't fully recommend. It's a stage-by-stage linear platformer with three irritations: The controls are a little too "snap-to-nearest" (like you will often initiate an unwelcome wall jump just because you jump near a wall) for me, the music is annoying, and I really want to get all the purple gems, but I can never tell whether I'm going the "right" way or the "wrong" way, and so I will often miss them just because of that. But I do basically like the game. I also started, for procrastination purposes, Humanity, which was recommended to me a while ago. It is good. The Steam videos do not do justice to how slick the game's graphics are (especially the UI has all these fluid little touches and impressive continuity as you transition between levels); I think it needs to run on a big monitor with a high frame rate. At its core it's mostly a puzzle game, with many things you have seen before, but also some new clever stuff (and I am only on the 2nd world, so I presume they have more surprises in store for me).

I believe this is all.




re

This halloween I am dressed as a withered husk, who was made this way by: Satisfactory 1.0

OMG. I can't believe October is over already. I blame Satisfactory which, okay, I do get it now, and it did destroy my body and mind. I am inches from being done now; I just want to make sure that I finish it with enough force that I do actually put it away, as I could imagine tinkering with my saddest factory forever.

The game isn't without flaw, but I think most of those flaws are not interesting to talk about. I do have one petty but important criticism, which is mildly spoilerful and anyway will only be interesting if you played the game. There is an object called the Somersloop ("cool S") which allows you to double the output of a machine. Canonically this item is some kind of "loop" and the flavor text talks about how it is able to create more energy than you put into it. So when I'm out hunting for Korok seeds I have this thought that maybe I could create a loop of factories whereby it would create infinite resources by repeatedly doubling. And I'm thinking about it but the crafting tree doesn't have any notable loops in it, but I remember the "packager" which allows you to put a fluid in a container or the converse, and I'm like: Yes, that's great! So I get back to base and I am doing this, just for fun to create an infinite fuel factory or whatever, and I realize that the packager just doesn't have a slot for a Somersloop. They must just hate fun, elegant twists. It would not break the game to allow this (you can always get infinite resources lots of other ways) or cause any other problem I can think of. Hmph!

The thing about constructing a factory and watching it churn is that it's basically the same thing as a programming project that you invented for yourself, and it's probably better to do the programming project. Here's progress on my mysterious rectangle:


Minusweeper 2


It's good progress if I do say so myself! Anything but black here is a Satisfactory result, which is 90.55% of them at this point. I may need heavy machinery for the remaining 9.45%, but that is part of the fun.

I think that's really it for this month! Please vote in the US Elections if you can (but I guess also vote in any important elections. And obviously, vote for the good guys???). And happy Halloween!




re

Card Deck Review: THE LEGEND OF SLEEPY HOLLOW TAROT

The Legend of Sleepy Hollow Tarot: Headless Horseman edition Nick Lawyer REDFeather (October 28, 2023) Reviewed by N. Richards What a wonderful way to honour the Irving Washington classic gothic story of 1822, “The Legend of Sleepy Hollow,” and the season of autumn as well as the art of Tarot all in one hit of […]

The post Card Deck Review: THE LEGEND OF SLEEPY HOLLOW TAROT first appeared on Hellnotes.




re

Book Review: THE EERIE BROTHERS AND THE WITCHES OF AUTUMN

The Eerie Brothers and the Witches of Autumn Sheldon Higdon Scary Dairy Press LLC (September 4, 2023) Reviewed by Nora B. Peevy The Eerie Brothers and the Witches of Autumn finds Horace and Edgar, the twin Eerie brothers, battling monsters to stop Hex from collecting one of the four globes to absorb the abilities of […]

The post Book Review: THE EERIE BROTHERS AND THE WITCHES OF AUTUMN first appeared on Hellnotes.




re

Book Review: INK VINE

Ink Vine Elizabeth Broadbent Undertaker Books (April 12, 2024) Reviewed by Nora B. Peevy When I heard Elizabeth Broadbent do a reading from Ink Vine, I knew I had to get my paws on that book. Her authenticity and unique voice, plus her sense of humor, shine through in this tale about standing up against […]

The post Book Review: INK VINE first appeared on Hellnotes.




re

Book Review: OF TEETH AND PINE

Of Teeth and Pine Desiree Horton Independently published (October 31, 2024) Reviewed by Nora B. Peevy Desiree Horton’s second novel is filled with blood, a taste of the beautiful outdoors, and a lot of snark. I fell in love with her main character, Vick, the female forest ranger who will not put up with any […]

The post Book Review: OF TEETH AND PINE first appeared on Hellnotes.




re

Book Review: WICKED ABANDONED

Wicked Abandoned A New England Horror Writers Anthology Edited by Rob Smales and Scott T. Goudsward Published by Wicked Creative, LLC  (September 25, 2024) Reviewed by Carson Buckingham All I have to say is that New England sure grows a bunch of great writers! Wicked Abandoned is one of the best anthologies I’ve read in […]

The post Book Review: WICKED ABANDONED first appeared on Hellnotes.




re

Suppressio Veri, Suggestio Falsi

In the aftermath of the appalling murder of an MP some commentators are looking at the occasionally poisonous comments made about politicians. The received wisdom of the public is that politicians are dishonest, but that is almost invariably a misreading. If MPs and others had to answer every question frankly, life would be impossible. Most of the usual questions would have to be answered with "I don't know" or "well, I hope that A happens but it might well be B for all I know." The Paxman figure would then rip the interviewee to shreds. So let's give them a break shall we?




re

Enough, Already!

This is not a political blog, although politics inevitably creep in to discussions of matters legal. I have followed politics since I was at school, although I was never elected to anything. The current situation beggars belief, and I imagine that today's crop of journalists will shake their heads in their old age, and say "but you should have been there in the summer of 2016; everything seemed to happen at once. " I am now even more convinced that my belief in the iron Law of Unintended consequences is the right one.

I have had to cut back on my sittings of late, as I am awaiting an operation to give me a new knee joint, and although I can get around in the courthouse it isn't always easy. As I am due to retire from the Bench in late October I have excused myself from getting to grips with some of the more complex innovations that have recently been introduced, such as iPads on the bench. I own a couple of iPads and I am comfortable with using them, but inevitably any government-issued software is over-engineered and the last thing from user-friendly.

My court has a few boxes that contain the iPads as well as charging them overnight, but those JPs who wish to use them have to submit to training as well as an elaborate procedure to keep them secure. It is worse for judges of course, but then they are paid £130k and more to cope.

Given my impending retirement, I cannot summon up the enthusiasm to get stuck in to this 21st century stuff  (albeit the technology is a decade old).

I am trying to avoid becoming what old Army types call demob-happy so I shall concentrate on justice before bureaucracy.






re

Supreme Chicken?

The Supreme Court is now considering a crucial case that will clarify the power of the judiciary vis a vis that of Parliament. Many of the country's finest legal minds will focus on this matter, and a verdict will be handed down. In the long tradition of European matters dividing our nation, some unscrupulous parties are attempting to discredit the Courts, in particular by focusing on individual judges and any perceived bias they may have. This is an appalling piece of vandalism, the worst offender being the Daily Mail. Recently that paper has given space to the risible Ian Duncan Smith, a failed Tory leader.  IDS' opinion reminds us how lucky we were to be spared his presence in Downing Street.

He repeats the now-customary jibe that judges are unelected. Of course they are, but then so are brain surgeons and airline captains, and we expect and receive a professional and disciplined service from them. Electing judges would fatally damage the public's confidence in the judiciary's utter impartiality.

We are blessed with a judiciary that is incorruptible, and that is why many foreign litigants choose to have their cases heard in London.

All judges and magistrates take the same judicial oath:-

 “I, _________ , do swear by Almighty God that I will well and truly serve our Sovereign Lady Queen Elizabeth the Second in the office of ________ , and I will do right to all manner of people after the laws and usages of this realm, without fear or favour, affection or ill will.”

That's good enough for me.




re

So. Farewell then Bystander...



We're very sorry to say that Bystander (real name Richard Bristow) died at Stoke Mandeville on June 4, aged 70. He was a Justice of the Peace at Uxbridge from 1985 to 2016,  and was the first chairman of the West London Local Justice Area. He'll be sadly missed by family and friends, but not by the villains of Uxbridge, Ealing and Hounslow.  

He was fond of quoting this passage from the Seven Ages of Man speech:

And then the justice,
In fair round belly with good capon lined,
With eyes severe and beard of formal cut,
Full of wise saws and modern instances;
And so he plays his part.




re

Bobsleighers want tracks covered

Great Britain's bobsleigh team call for all sliding tracks to be covered after heavy snowfall at the Winter Youth Olympics in Austria.




re

Sochi's Winter Olympic preparations 'impressive'

Ski Sunday presenter Ed Leigh is wowed by Sochi two years ahead of the 2014 Winter Olympics




re

Yarnold secures skeleton bronze

Great Britain's Elizabeth Yarnold wins a bronze medal at the women's skeleton World Championship.




re

BORROWED TIME release and launch photo report!

As of November 10, 2015, BORROWED TIME (the sequel to CHRONAL ENGINE) is now available in bookstores everywhere as well as online (in hardcover and ebook)!  Signed copies are available from BookPeople.

In an article titled, 'Borrowed Time' mixes paleontology and fantasy, Saturday's Austin American-Statesman had a great review of BORROWED TIME, stating it's "a slam-dunk for dinosaur aficionados and will appeal as well to those who are fans of literary time travel and outdoorsy adventure."

Sunday was the launch party at BookPeople! I had great fun doing a presentation discussing the connections between the book, Charles Umlauf, dinosaurs, Johnny Weissmuller, and me (really).

The dinosaur standees for the photo booth were a hit, as were the refreshments including water, soft drinks, wine and cheese, and crackers. (The wine, from the Languedoc region of France, is made from grapes grown in Cretaceous clays where dinosaur fossils have have been found).

But the real eye-opener was the mosasaur cake by author/cakelustrator Akiko White. About two feet high, it featured a mosasaur sculpted from modeler's chocolate on a chocolate cake base with buttercream frosting! She'll be doing a youtube video on the making of it soon (and I'll link when it's available).  Suffice to say that still pictures don't do it justice -- it was mounted on a motorized turntable and illuminated with a blue strobe that made it look like it was underwater!

Here are the pics:

Me and cake

Carmen Oliver and T.rex
Akiko assembles! (photo courtesy Akiko White)
Presenting (photo courtesy Akiko White)
Cake!
Refreshments
Signing
Frances Hill and Lindsey Lane (photo courtesy of Shelley Ann Jackson)
Shelley Ann Jackson and Lindsey Lane (photo courtesy Shelley Ann Jackson)
 Many thanks to BookPeople for hosting the event, to everyone who came for the event, and to everyone who helped out: Akiko, for making the awesome cake; Cynthia Leitich Smith; Carmen Oliver; Lindsey Lane; Shelley Ann Jackson; and Cory Putnam Oakes!

Cake topper in its natural habitat






re

Pizza a Day Diet: Star Trek Pizza

A few years back, when Cynthia Leitich Smith was off to Vermont for the VCFA residency, I undertook an exploration of Austin pizza joints and pizza blogging: the rules were these: aside from a dinner salad prior to the pizza, my meals were pizza for breakfast, lunch, and dinner.  For ten days.  For the record, the first time I did it, I lost five pounds; the second time, two and a half.

Here's the inaugural post from 2009:  A Pizza a Day and Other Weird Activities.

I tried this again January 2015, but posted only to my Facebook account (I'll be reproducing the posts here along with this edition, with the term "archive" in the header).  I also did it in July 2015.

To view the entire line-ups, just click the "pizza a day" label.

This time, I decided to do something a little different, since I'm on the verge of exhausting Austin's specialty pizza places:  I'm going to see how many pizzas I can make using various techniques.  I'll also take a look at some of the places I've missed or have recently opened.

And, for Christmas, I received this nifty little item:

Yes, it is a starship Enterprise pizza cutter.  So of course I had to make a couple Star Trek-inspired pizzas:
(You can see the Enterprise if you squint real hard).  The saucer section was Canadian bacon with an olive for the bridge.  The nacelles were scallions and the engineering section Belgian endive.  The pizza didn't turn out so great but the cutter worked fantastically.

I also made a pizza in honor of our Klingon allies:

This one sort of drifted apart due to migration of the mozzarella, but it is a Klingon D7 class battlecruiser.  The main hull was a green pepper, while the nacelle supports were red onion.  The nacelles themselves, and the neck section, were scallions, and the bridge was a mushroom slice.

Q'apla!



  • pizza a day
  • Pizza a Day Diet

re

Pizza a Day Diet: Austin Beer Garden Brewing Co. (The ABGB)

Today's pizza a day diet pizza came from the Austin Beer Garden Brewing Co. at 1305 W. Oltorf (right next to the train tracks).

I hit the place in mid-afternoon, so it was pretty empty (Happy hour is from 3 pm to 7 pm, though, so it filled quickly :-)).  You order food and beer at the bar and they bring it to your table.  Inside are long wooden tables with benches, for social/communal beer-gardening in the Bavarian tradition.  Outside are round tables under the live oaks for beer gardening in the Austin tradition. :-).


I ordered a sausage pizza (boring, I know :-), but I like to try new places out on the basics).  It was delivered hot and fresh; the crust was somewhat soft but firmed up after I let it cool a little.  It had a nice chew and stood up to the ingredients.  The sausage had a more subtle flavor than I was expecting, but I really liked it and its freshness.  The cheese and sauce were also quite good.


One of their "by the slice" choices had also caught my eye, so I ordered it as well.  This was venison, spinach, pesto, white bean, roasted tomato, roasted garlic, and ricotta.  This one was amazing (not that the sausage was bad).  The crust had just the right amount of crispness and chew, but the combination of toppings really made it.  It had a richness from the venison without being gamy or overwhelming, and the remaining ingredients provided a terrifically contrasting texture in every bite.


Oh, and the beer was darn good, too. :-).







  • pizza a day
  • Pizza a Day Diet

re

Texas Library Association Conference (#txla16)

Just back from the Texas Library Association Conference in Houston! 

As always, it was great to see fellow authors and illustrators, as well as the librarians who've supported our books through the years.  And I always enjoy seeing what's new at the publisher booths.

We had a great time for my panel Tuesday afternoon, "What's New with Texas Middle Grade and YA Authors," organized by Susie Kralovansky, featuring Jessica Lee Anderson moderating, and fellow panelists Paige Britt, Cory Putnam Oakes, P.J. Hoover, Cynthia Levinson, Liz Garton Scanlon, Jennifer Mckissack, and Joy Preble.  Conversation was entertaining and enlightening.  

Many thanks to everyone who puts in the work to make TLA the best state library conference in the country!

Here are some pics:

Hitting the road
Rainy Houston from the hotel
Me, Cynthia Levinson, PJ Hoover
Carmen Oliver signs
Jennifer McKissack, Jennifer Ziegler, Joy Preble
Paige Britt and Donna Janell Bowman
Me and the world in the lobby of the Hilton
Signing BORROWED TIME
Janet Fox and Jennifer Ziegler
PJ, Jessica, and Joy
Me and Elaine Scott
Buffalo Bayou on my early morning run
Back in Austin!
   





re

Midwest Schools and Bookstores

I'm just back from a twelve day trip up to Chicago, Milwaukee, and Minneapolis-St. Paul, where I did a bit of research and visited a bunch of schools and children's indie bookstores.

The trip started inauspiciously, when my flight was canceled because the wind blew the plane onto a belt conveyor.

Eventually, I made it to Chicago, though, where the weather looked like this:
Still, I had arrived ahead of time so I could go down to the Museum of Science and Industry, which has a World War II German u-boat and a chicken incubator.
Next two days were the actual school visits, arranged at Henry Puffer Elementary  and Liberty Elementary by Anderson's Book Shop and at Attea Glenview School and Rondout School by The Book Stall.  Afterwards, I got to hang out with Robert from The Book Stall and stopped by for a couple of pics.
Posing with posters

 

Then I was off to Milwaukee for a school visit at Atwater Elementary arranged through the Boswell Book Company.
It was my first time I'd ever been to Milwaukee, but sadly didn't have a chance to sightsee, because it was off to Minneapolis-St. Paul for three days of school visits.

Visits at North Trail Elementary and Brimhall Elementary were through Addendum Books; those at Crestview Elementary and Little Canada Elementary were through the Red Balloon Bookshop; and at Valley View Middle School, through Wild Rumpus Books.
Snake!

I had some free time, so I went over to Addendum Books for some pics and had a fun lunch with Katherine and Marcus, the proprietors.

In front of the "Purple Rain" wall

Since I was there over the weekend, I spoke at Red Balloon for the Minnesota SCBWI about Research and the Suspension of disbelief.

I also had the chance to go run a couple times on the Mississippi Riverfront trail and visit the Science Museum of Minnesota.

 
T.rex!
Triceratops
Stegosaurus!
After Monday's school visit I had a fun lunch with Drew and Jordan of Wild Rumpus Books at Pizzeria Lola (a separate Pizza-a-Day Diet post will be forthcoming).  Then I visited the bookstore, where I met the menagerie.
Copper oven and decorative birch logs
Chicken!
Ferret!
Then I was back to Chicago and spent a day at the Field Museum of Natural History and showed Madeline Smoot of CBAY Books a bit of the city!

 

Many thanks to all the librarians and booksellers and Blue Slip Media and everyone else who made this happen.  Thanks also to Quinette Cook and all the folks from MN SCBWI who came out for the workshop.  It was great fun meeting you!

For information on how to book me for school visits for the 2016-2017 school year, contact Carmen Oliver at The Booking Biz.












re

Capital of Texas Triathlon Preview

Monday I'm going to be running in the 25th Capital of Texas Triathlon!  It's my first triathlon (Olympic distance) in twenty years and I'm pretty jazzed.

Steely-eyed determination 20 years ago. :-)

One of the great things about triathlons (and running races in general) is that you get to occupy unusual spaces: the last ones I did were Leon's Triathlon in Hammond, Indiana, a couple of Bud Light triathlons and others in Chicago. Leon's had a swim in Wolf Lake (shudder), followed by a cycle leg on an elevated highway that ran past the old U.S. Steel plant, and a run leg through an industrial downtown.  The Chicago ones were on the lakefront, just north of Navy Pier, with a bike on Lake Shore Drive and a run along the lake.

2013 CapTexTri
The CapTexTri also has a great location in downtown Austin, with a 1.5k swim in Lady Bird Lake; a 40k (24.8 mile) bike on a quadruple loop through downtown Austin, including Congress Avenue and Cesar Chavez; and a 10k (6.2 mile) run through Zilker Park.

The only thing I'm not too keen on is the bike route, since it requires you to do the same loop four times with a bunch of corresponding hairpin turns.  I don't like loop routes because I always think of how many more times I have to do the thing...Still, going up and down Congress Avenue without any cars is going to be pretty cool. As long as there are no poles in the middle of the road, I should be okay. :-).

Don't ask.

I feel fairly good about my training.  I've maintained good running mileage after the Austin Marathon and Austin Distance Festival and got some good workouts in even while traveling doing school visits.
On Stone Arch Bridge in Minneapolis
The swim is probably my weakest event -- I could stand to do more work on technique and probably do more open water swimming, but the distance won't be an issue.  Also, Lady Bird Lake isn't going to have waves (I seem to recall a couple of triathlons in Chicago with 3-4 foot waves on Lake Michigan (and this was on the near side of the breakwater).  Also, I won't have to deal with a wet suit.  My biggest concern is to not get kicked in the face. :-).
Lady Bird Lake during 2013 CapTexTri
The bike I'm feeling good about as well.  I'll be using the bike I used for my triathlons back in the day - a Trek 1000 I bought when I was in grad school for $450 (a guy at one bicycle shop here tried to sell me a new one, asking if I had a "nostalgic attachment" to it.). I do, but I also don't think a new bike is going to drastically transform my performance.  At least not $2000 worth :-). (A guy at another bike shop told me the Trek 1000 was his first road bike and he wished he still had it.  It's possible he was being kind :-)).

Tomorrow is packet pick-up, bike drop-off, and a chance to scope out the transition area, which I'll need because I can't see without my glasses...:-)

Oh, well.  Qapla!















re

Star Trek: The Cruise 2017!

Ever since November 2, 2016, I've been meaning to blog about the Cubs winning the World Series, and I may yet, but I think my post about their getting into the World Series ("Every Cubs Fan is Ten Years Old Tonight") pretty much sums things up (though obviously it didn't cover their blowing the lead in game 7, extra innings, and that rain delay...).

But for now, I thought I'd blog about what happened this year, between January 9 and 15, when I boarded a very large ship for the first time and set sail with some 2000+ like-minded individuals for the first annual Star Trek Cruise.

All the elevators were decorated like this
Many of the restaurants and bars were similarly themed...
It was an absolutely fantastic and fun experience: the cruise ship was decorated (as much as possible on a finite budget) to recreate Federation technology and decor and there were activities and performances and autograph and photo opportunities by and with the likes of William Shatner, John deLancie, Denise Crosby, Marina Sirtis, Terry Farrell, Ethan Phillips, Max Grodenchik, Robert O'Reilly, Casey Biggs, and Chase Masterson.

My mess dress uniform for the formal
There were also four official theme nights: (i) A Night in the Holodeck, where you were supposed to dress as your favorite holodeck/holosuite character; (ii) Q's Masquerade Ball, where the sky was the limit; (iii) the Captain's Formal Gala; and (iv) an Evening on Risa.

I first heard about the cruise back in August of 2015 or so, when it was first announced, and decided that I really had to go. Now, although I've been a Star Trek fan since I was a kid, I've never been to a con or on a cruise, but there was just something about this idea that I found fascinating. 

So I decided I'd go, and I made the affirmative decision that I was going to embrace the cosplay.  Granted, I didn't have to, and there were a significant number of folks who ended up wearing Star Trek-related garb or generically appropriate clothing, but I decided that I would dive in, as it were (In part, I did so because half the fun of a vacation is anticipating it, and preparing the cosplay was an engaging extension.

At first, I debated doing makeup and going all out as a Trill or a Vulcan (and took the appropriate
Go Niners!
accoutrements onto the ship, but ended up not going that extra step for logistical reasons).  And there were a couple costume ideas I considered and then discarded as either too unwieldy or just not right.

Eventually, though, I decided to do costumes not based on individual particular characters per se, but ones that would evoke an episode or the world of an episode (Incidentally, I tend to think this is one of the reasons for the appeal of books like Harry Potter or Lord of the Rings. Readers who like imaginative play like to imagine themselves in those worlds, perhaps more so than they imagine themselves as Harry Potter or Frodo).

So, what were my favorite episodes that lent themselves to cosplay? Well, for the favorite holodeck character, I decided early on that I wanted to do something related to baseball, since it was the favorite game of Captain Sisko on Deep Space Nine and the subject of one of the more goofily charming episodes, "Take Me Out to the Holosuite," in which a Vulcan captain challenges Sisko and the crew of DS9 to a baseball game.  Both sides had their own uniforms and the DS9 crew were the "Niners."

I ended up having problems figuring out how to do the uniform in a screen accurate manner, so decided to wing it and do what I ended up calling a "Deep Space Nine throwback uniform," complete with Terok Nor (the former name of Deep Space Nine) logo. :-). As you can see, though, there were a number of people who had less difficulty than I did in getting a screen accurate uniform :-):
  

And people were already pulling out the stops for the evening. Here are a couple pics:

The Q Continuum made an appearance
Really creative holodeck no. 1
A transport in progress. The photo doesn't really do it justice
Really creative holodeck no. 2
A very nice historical look

For Q's Masquerade, I found myself with a dilemma: the idea was that it would be a masked ball, but when you wear eyeglasses, masks can prove to be a bit difficult.  In the end, I went with a wild west outfit, because I figured I could wear an "outlaw bandana" as the mask.

The costume is based on the worlds of either the Original Series episode "Spectre of the Gun" or the Next Generation episode "Fistful of Datas." Ultimately, I kept the mask in my pocket since it interfered with the eating and drinking.

As you can see, there were a number of folks who had similar ideas:


They had to change the venue for the masquerade due to weather, so I didn't get as many shots as I'd've liked, but here are a few:
 
 
 
 
 



For the Captain's Formal Gala, I decided that I could wear my tux, but I'd always liked the formal dress uniform that was introduced in Star Trek: Insurrection and also appeared in DS9, so I went with that.  I confess that at first I had thought it would be the most boring night of cosplay, since everyone would be in Star Fleet formal uniforms.  And people did that, but there was a bit of variety as well...

 
 
 
 
 
 
The last official theme night, an evening and barbecue ("Targ-BQ") on Risa, seemed to be the most troublesome costume-wise, or at least the one that stretched people's creativity, especially since the only really distinctive Risan outfit was Picard's infamous "silver speedo."  There actually were brave folks who went with that, but one of my favorites was the couple who dressed up as George and Gracie, the humpback whales from Star Trek IV.

And what am I wearing? A conventional pair of board shorts and a garment called a "sleeveless hoodie." It's actually off the rack, but I did have someone ask me if I'd made it myself. I bought it because I'd never heard of such a thing before and the idea of a sleeveless hoodie seemed kind of bizarre to me and therefore suitably Risan :-).

Another of my favorites of the night was The Game headsets (from the eponymous Next Gen episode) that a couple of guys brought:
And then there were these guys whose three hour tour seemed to have gotten a bit sidetracked:

There was a lot of other stuff going on, as well, including the chance to randomly encounter the actors...

...pictures and autographs...
 
 
 
 
The Grand Nagus signed my novel! :-)
...shore excursions...
 
 
 
...on board lectures, panels, and performances...
William Shatner "christens" the cruise
Terry Farrell leads yoga

Lolita Fatjo discusses script coordinating...

Terry Farrell, Chase Masterson, and James Darren

Casey Biggs leads a wine tasting
...and, of course, the great tribble hunt:
There was way too much going on for me to have caught even a fraction of it, but you can see a ton of pictures at the 2017 Star Trek Cruise photo gallery and there's a great video here.

You can also check out the blog reports from StarTrek.com here: Day 1, Day 2, Day 3, Day 4, Day 5, Day 6.

Live long and prosper!






re

2018 Releases by Austin Authors and Illustrators

It's the eve of the Texas Book Festival so make sure you check out the festivities on the Capitol grounds! And here's a tentative list of projects from Austin authors and illustrators releasing next year! For previous years, go here.

Picture Books, Easy Readers, and Board Books

WHAT DO YOU DO WITH A VOICE LIKE THAT? by Chris Barton, ill. by Ekua Holmes (Simon & Schuster/Beach Lane 2018).

MIGHTY TRUCK: ON THE FARM, by Chris Barton, ill. by Troy Cummings (HarperCollins, May 2018).

MIGHTY TRUCK: THE TRAFFIC TIE-UP, by Chris Barton, ill. by Troy Cummings (HarperCollins, May 2018).

PENGUIN AND TINY SHRIMP DON'T DO BEDTIME, by Cate Berry, ill. by Charles Santoso (Balzer + Bray/HarperCollins 2018).

ABRAHAM LINCOLN'S DUELING WORDS, by Donna Janell Bowman, ill. by S.D. Schindler (Peachtree, 2018)

THE BOOK THAT JAKE BORROWED, by Susan Kralovansky (Pelican 2018).

COUNTING COLORS IN TEXAS, by Susan Kralovansky (Pelican 2018).

KATE, WHO TAMED THE WIND, by Liz Garton Scanlon, ill. by Lee White (Schwartz & Wade, Spring 2018).

FRANCES IN THE COUNTRY, by Liz Garton Scanlon, ill. by Sean Qualls (Neal Porter Books/Roaring Brook Press, Summer 2018).

DEAR SUBSTITUTE, by Liz Garton Scanlon, ill. by Chris Raschka (Disney-Hyperion, Summer 2018).

STALEBREAD CHARLIE AND THE RAZZY, DAZZY SPASM BAND,  by Michael Mahin, ill. by Don Tate (Houghton Mifflin Harcourt, 2018).

PAR-TAY: DANCE OF THE VEGGIES (AND THEIR FRIENDS), by Eloise Greenfield, ill. by Don Tate (Alazar Press 2018).

POTATO KING: THE STORY OF JUNIUS G. GROVES, by Don Tate (Knopf 2018).
 

Middle Grade

THE BOY, THE BOAT, AND THE BEAST, by Samantha Clark (Paula Wiseman Books/Simon & Schuster, Summer 2018).

KNOCKOUT, by K.A. Holt (Chronicle, Spring 2018).

DEAR ME (tent. title), by K.A. Holt (Scholastic, Summer 2018).

THE PARKER INHERITANCE, by Varian Johnson (Scholastic, Spring 2018).

THE CAMELOT CODE: THE ONCE AND FUTURE GEEK, by Mari Mancusi (Disney-Hyperion, Oct. 2018).

GIRLS WHO CODE: LIGHTS, MUSIC, CODE, by Jo Whittemore (Penguin Workshop, Spring 2018).

REVENGE OF THE TEACHER'S PETS, by Jennifer Ziegler (Scholastic, June 2018).

Young Adult

HEARTS UNBROKEN, by Cynthia Leitich Smith (Candlewick Press, 2018).

AVENGED, by Amy Tintera (HarperTeen, May 2018).




re

Serendipity, a super-Jupiter, and saving VIPER

This was a big week in space, from Curiosity stumbling upon sulfur crystals to an exoplanet discovery and a major advocacy effort.




re

Eureka? Scientists’ first hints of life on other planets may not be so obvious

Knowing that you've found signs of life beyond Earth may not be as clear-cut and simple as one might think.




re

How EELS could change the future of robotic exploration

The snake-like robot is being designed to autonomously navigate the challenging terrain of Saturn’s moon Enceladus, including descending into fissures in the moon’s icy crust. The skills it needs in order to explore this distant, unfamiliar world may make EELS well equipped to explore even more alien worlds, perhaps including exoplanets.




re

A billion dollars short: A progress report on the Planetary Decadal Survey

NASA is underfunding planetary exploration relative to recommendations made by the National Academies Decadal Survey report, resulting in mission delays and cancelations.




re

Explore the Cosmos with The Planetary Society and Lerner Publishing

The Planetary Society and Lerner Publishing Group have teamed up to bring young readers an engaging series of books that make space science fun and accessible.




re

Extraterrestrial artifacts

Could the Solar System host traces of other intelligent life?




re

Where Congress Stands on NASA's 2025 budget

Weeks before the new fiscal year, Congress still hasn't finalized NASA's 2025 budget.




re

Streaks and highlights

It’s been a great year for space exploration. Now you get to pick the highlights.




re

Glenmorangie A Tale of Ice Cream Single Malt Scotch Whisky




re

Christmas jewelry in progress.

Sneak peek — Christmas jewelry in progress.




re

Christmas serving board in progress.

Christmas serving board in progress.




re

Signed and personalized at request!

We just made the decision to ship the remaining copies of Feast to us. There are about 800 left, out of the original 2000 copy print run. Which is a lot of copies, but given that we were originally scheduled to launch March 2020, and had to cancel an entire summer’s worth of scheduled bookstore […]




re

Creating Draugr

I have an idea of making a few stands of Draugr, the Viking dead. I have bought some nice Colin Pattern sculpts but thought I'd have a go at making some.
I have loads of Viking sprues left over from the Lake town project, these I mixed with some Oathmark undead sprues. I will add some greenstuff to these to make them a little more Viking.


 




re

Eugene Zaikonnikov: Breaking the Kernighan's Law

"Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it.." — Brian W. Kernighan.

I'm a sucker for sage advice much as anyone else, and Kernighan is certainly right on money in the epigraph. Alas there comes a time in programmer's career when you just end up there despite the warning. It could be that you were indeed too clever for your own good, or maybe the code isn't quite yours anymore after each of your colleague's take on it over the years. Or just sometimes, the problem is indeed so hard that it strains your capacity as a coder.

It would usually start with a reasonable idea made into first iteration code. The solution looks fundamentally sound but then as you explore the problem space further it begins to seep nuance, either as manifestation of some real world complexity or your lack of foresight. When I run into this my first instinct is to instrument the code. If the problem is formidable you got to respect it: flailing around blindly modifying things or ugh, doing a rewrite at this stage is almost guaranteed to be a waste of time. It helps to find a promising spot, chisel it, gain a foothold in the problem, and repeat until you crack it. Comfortable debugging tools here can really help to erode the original Kernighan coefficient from 2 to maybe 1.6 or 1.4 where you can still have a chance.

Lisp users are fortunate with the options of interactive debugging, and one facility I reach often for is the plain BREAK. It's easy enough to wrap it into a conditional for particular matches you want to debug. However sometimes you want it to trigger after a particular sequence of events across different positions in code has taken place. While still doable it quickly becomes cumbersome and this state machine starts to occupy too much mental space which is already scarce. So one day, partly as a displacement activity from being intimidated by a Really Hard Problem I wrote down my debugging patterns as a handful of macros.

Enter BRAKE. Its features reflect my personal preferences so are not necessarily your cup of tea but it could be a starting point to explore in this direction. Things it can do:

  • act as a simple BREAK with no arguments (duh)
  • wrap an s-expression, passing through its values upon continuing
  • trigger sequentially based on the specified position for a common tag
  • allow for marks that don't trigger the break but mark the position as reached
  • provide conditional versions for the expressions above
  • print traces of tagged breakpoints/marks

If you compile functions with debug on you hopefully should be able to see the wrapped sexpr's result values.

(use-package '(brake))

(defun fizzbuzz ()
  (loop for n from 100 downto 0
	for fizz = (zerop (mod n 3))
	for buzz = (zerop (mod n 5)) do
	(format t "~a "
		(if (not (or fizz buzz))
		    (format nil "~d" n)
		  (brake-when (= n 0)
			      (concatenate 'string
					   (if fizz "Fizz" "")
					   (if buzz "Buzz" "")))))))

These macros try to detect common cases for tagged sequences being either aborted via break or completed to the last step, resetting them after to the initial state. However it is possible for a sequence to end up "abandoned", which can be cleaned up by a manual command.

Say in the example below we want to break when the two first branches were triggered in a specific order. The sequence of 1, 3, 4 will reinitialize once the state 4 is reached, allowing to trigger continuously. At the same time if we blow our stack it should reset to initial when aborting.

(defun ack (m n)
  (cond ((zerop m) (mark :ack 3 (1+ n)))
        ((zerop n) (mark :ack 1 (ack (1- m) 1)))
        (t (brake :ack 4 (ack (1- m) (ack m (1- n)))))))

In addition there are a few utility functions to report on the state of brakepoints, enable or disable brakes based on tags and turn tracing on or off. Tracing isn't meant to replace the semantics of TRACE but to provide a souped up version of debug by print statements everyone loves.

CL-USER> (report-brakes)
Tag :M is DISABLED, traced, with 3 defined steps, current state is initial
Tag :F is DISABLED with 2 defined steps, current state is 0
Tag :ACK is ENABLED with 3 defined steps, current state is initial

Disabling breakpoints without recompilation is really handy and something I find using all the time. The ability to wrap a sexpr was often sorely missed when using BREAK in constructs without implicit body.

Sequencing across threads is sketchy as the code isn't guarded but in many cases it can work, and the appeal of it in debugging races is clear. One of those days I hope to make it more robust while avoiding potential deadlocks but it isn't there yet. Where it already shines tho is in debugging complex iterations, mutually recursive functions and state machines.




re

Scott L. Burson: Comparison: FSet vs. Sycamore

[BULLETIN: Quicklisp now has the latest version of FSet.]

Sycamore, primarily by Neil Dantam, is a functional collections library that is built around the same weight-balanced binary tree data structure (with leaf vectors) that FSet uses.  While the README on that page comments briefly on the differences between Sycamore and FSet, I don't feel that it does FSet justice.  Here is my analysis.

Dantam claims that his library is 30% to 50% faster than FSet on common operations.  While I haven't done comprehensive micro-benchmarking, a couple of quick tests indicates that this claim is plausible.  A look through the internals of the implementation confirms that it is clean and tight, and I must commend him.  There may be some techniques in here that I could usefully borrow.

Most of the performance difference is necessitated by two design choices that were made differently in the two libraries.  One of these Dantam mentions in his comparison: FSet's use of a single, global ordering relation implemented as a CLOS generic function, vs. Sycamore's more standard choice of requiring a comparison function to be supplied when a collection is created.  The other one he doesn't mention: the fact that FSet supports a notion of equivalent-but-unequal values, which are values that are incomparable — there's no way, or at least no obvious way, to say which is less than the other, and yet we want to treat them as unequal.  The simplest example is the integer 1 and the single-float 1.0, which have equal numerical values (and cl:= returns true on them), but which are nonetheless not eql.  (I have a previous blog post that goes into a lot more detail about equality and comparison.)  Since Sycamore expects the user-supplied comparison function to return an integer that is negative, zero, or positive to indicate the ordering of its arguments, there's no encoding for the equivalent-but-unequal case, nor is there any of the code that would be required to handle that case.

Both of these decisions were driven by my goal for the FSet project.  I didn't just want to provide a functional collections library that could be called occasionally when one had a specific need for such a data structure.  My ambition was much grander: to make functional collections into a reasonable default choice for the vast majority of programming situations.  I wanted FSet users (including, of course, myself) to be able to use functional collections freely, with very little extra effort or thought.  While Lisp by itself reaches a little bit in this direction — lists can certainly be used functionally — lists used as functional collections run into severe time complexity problems as those collections get large.  I wanted the FSet collections to be as convenient and well-supported as lists, but without the time complexity issues.

— Or rather, I wanted them to be even more convenient than lists.  Before writing FSet, I had spent years working in a little-known proprietary language called Refine, which happened to be implemented on top of Common Lisp, so it was not unusual to switch between the two languages.  And I had noticed something.  In contrast to CL, with its several different predefined equality predicates and with its functions that take :test arguments to specify which one to use, Refine has a single notiion of equality.  The value space is cleanly divided between immutable types, which are compared by value — along with numbers, these include strings, sets, maps, and seqs — and mutable objects, which are always compared by identity.  And it worked!  I found I did not miss the ability to specify an equality predicate when performing an operation such as "union".  It was just never needed.  Get equality right at the language level, and the problem goes away.

Although FSet's compare generic function isn't just for equality — it also defines an ordering that is used by the binary trees — I thought it would probably turn out to be the case that a single global ordering, implemented as a generic function and therefore extensible, would be fine the vast majority of the time.  I think experience has borne this out.  And just as you can mix types in Lisp lists — say, numbers and symbols — without further thought, so you can have any combination of types in an FSet set, effortlessly.  (A project I'm currently working on actually takes considerable advantage of this capability.)

As for supporting equivalent-but-unequal values, this desideratum flows directly from the principle of least astonishment.  While it might not be too surprising for a set or map implementation to fail distinguish the integer 1 from the float 1.0, it certainly would be very surprising, and almost certainly a source of bugs in a compiler that used it, for it to fail to distinguish two uninterned symbols with the same name.  (I saw a macro expansion recently that contained two distinct symbols that both printed as #:NEW.  It happens.)  A compiler using Sycamore for a map on symbols would have to supply a comparison function that accounted for this; it couldn't just compare the package name and symbol name.  (You'd have to do something like keep a weak hash table mapping symbols to integers, assigned in the order in which the comparison function encountered them.  It's doable, but FSet protects you from this madness.)

Along with those deep semantic design choices, I've spent a lot of time on developing a wide and featureful API for FSet (an effort that's ongoing).  FSet has many features that Sycamore lacks, including:

  • seqs, a binary-tree sequence implementation that holds arbitrary Lisp objects (Sycamore ropes hold only characters, which is certainly an important special case, but why restrict ourselves?)
  • default values for maps and seqs (the value to return when the key is outside the domain is associated with the collection, not supplied at the call site; this turns out to be a significant convenience)
  • generic functions that operate on both lists and FSet collections, to shadow the CL builtins
  • the powerful map-union and map-intersection operations (I'll blog about these in the future)
  • more ways to iterate over the collections (the FSet tutorial has a good summary, about 3/4 of the way down)
  • speaking of the tutorial, FSet has lots more documentation

Let me digress slightly to give an example of how FSet makes programming more elegant and convenient.  Joe Marshall just put up a blog post comparing Go(lang) with Common Lisp, which is worth a read on its own; I'm just going to grab a code snippet from there to show a little bit of what programming with FSet is like.  Here's Joe's code:

 (defun collate (items &key (key #'identity) (test #'eql) (merger (merge-adjoin #'eql)) (default nil))
   (let ((table (make-hash-table :test test)))
     (dolist (item items table)
       (let ((k (funcall key item)))
         (setf (gethash k table) (funcall merger (gethash k table default) item))))))

 (defun merge-adjoin (test)
   (lambda (collection item)
     (adjoin item collection :test test)))

And here's what I would write using FSet:

 (defun collate (items &key (key #'identity))
   (let ((result (map :default (set))))
     (dolist (item items result)
       (includef (@ result (funcall key item)) item))))

(Well, I would probably move result outside the dolist form to make it clearer what the return value is, but let's go with Joe's stylistic choice here.)

For those who haven't used FSet: the form (map :default (set)) creates a map whose default is the empty set, meaning that lookups on that map will return the empty set if the key is not in the map.  This saves the includef form from having to handle that possibility.

My version makes assumptions, it's true, about how you want to collect the items with a given key; it doesn't give you other choices.  It could, but what would be the point?  It's already using a general set with better time complexity than lists, and saving you from having to write anything like merge-adjoin.  The extensible global equivalence relation means you're not going to need to supply a :test either.

I think the FSet-enhanced code is cleaner, more elegant, and therefore clearer than the plain-CL version.  Don't you agree?  Maybe you wouldn't say it's a huge improvement, okay, but it's a small example; in a larger codebase, I would argue, these small improvements add up.

* * * * *

To summarize: if you just want a library you can call in a few places for specific purposes, Sycamore might work better for you (but think hard if you're writing a comparator for symbols).  FSet can certainly be used that way, but it can be much more.  If you want to see one way in which Common Lisp can be made into a better language, without giving up anything that we love about it, I urge you to give FSet a try.

FSet has changed the way I write Lisp programs.  — an FSet user

(UPDATE: the magnitude of the performance difference between FSet and Sycamore surprised me, and inspired me to do some profiling of FSet.  It turned out that I could get a 20% speedup on one micro-benchmark simply by adding some inline declarations.  Mea culpa, mea culpa, mea maxima culpa; I should have done this years ago.   With that change, the generic function overhead appears to be the only significant cause of the remaining ~20% performance difference.  I tried creating a Sycamore set using a thin wrapper around fset:compare, and the resulting performance was very similar to that of FSet with its new inlines.)




re

TurtleWare: Dynamic Vars - A New Hope

Table of Contents

  1. Dynamic Bindings
  2. The problem
  3. The solution
  4. Dynamic slots
  5. The context
  6. Summary

Dynamic Bindings

Common Lisp has an important language feature called dynamic binding. It is possible to rebind a dynamic variable somewhere on the call stack and downstream functions will see that new value, and when the stack is unwound, the old value is brought back.

While Common Lisp does not specify multi-threading, it seems to be a consensus among various implementations that dynamic bindings are thread-local, allowing for controlling the computing context in a safe way.

Before we start experiments, let's define a package to isolate our namespace:

(defpackage "EU.TURTLEWARE.BLOG/DLET"
  (:local-nicknames ("MOP" #+closer-mop "C2MOP"
                           #+(and (not closer-mop) ecl) "MOP"
                           #+(and (not closer-mop) ccl) "CCL"
                           #+(and (not closer-mop) sbcl) "SB-MOP"))
  (:use "CL"))
(in-package "EU.TURTLEWARE.BLOG/DLET")

Dynamic binding of variables is transparent to the programmer, because the operator LET is used for both lexical and dynamic bindings. For example:

(defvar *dynamic-variable* 42)

(defun test ()
  (let ((*dynamic-variable* 15)
        (lexical-variable 12))
    (lambda ()
      (print (cons *dynamic-variable* lexical-variable)))))

(funcall (test))
;;; (42 . 12)

(let ((*dynamic-variable* 'xx))
  (funcall (test)))
;;; (xx . 12)

Additionally the language specifies a special operator PROGV that gives the programmer a control over the dynamic binding mechanism, by allowing passing the dynamic variable by value instead of its name. Dynamic variables are represented by symbols:

(progv (list '*dynamic-variable*) (list 'zz)
  (funcall (test)))
;;; (zz . 12)

The problem

Nowadays it is common to encapsulate the state in the instance of a class. Sometimes that state is dynamic. It would be nice if we could use dynamic binding to control it. That said slots are not variables, and if there are many objects of the same class with different states, then using dynamic variables defined with DEFVAR is not feasible.

Consider the following classes which we want to be thread-safe:

(defgeneric call-with-ink (cont window ink))

(defclass window-1 ()
  ((ink :initform 'red :accessor ink)))

(defmethod call-with-ink (cont (win window-1) ink)
  (let ((old-ink (ink win)))
    (setf (ink win) ink)
    (unwind-protect (funcall cont)
      (setf (ink win) old-ink))))

(defclass window-2 ()
  ())

(defvar *ink* 'blue)
(defmethod ink ((window window-2)) *ink*)

(defmethod call-with-ink (cont (win window-2) ink)
  (let ((*ink* ink))
    (funcall cont)))

The first example is clearly not thread safe. If we access the WINDOW-1 instance from multiple threads, then they will overwrite a value of the slot INK.

The second example is not good either, because when we have many instances of WINDOW-2 then they share the binding. Nesting CALL-WITH-INK will overwrite the binding of another window.

The solution

The solution is to use PROGV:

(defclass window-3 ()
  ((ink :initform (gensym))))

(defmethod initialize-instance :after ((win window-3) &key)
  (setf (symbol-value (slot-value win 'ink)) 'red))

(defmethod call-with-ink (cont (win window-3) ink)
  (progv (list (slot-value win 'ink)) (list ink)
    (funcall cont)))

This way each instance has its own dynamic variable that may be rebound with a designated operator CALL-WITH-INK. It is thread-safe and private. We may add some syntactic sugar so it is more similar to let:

(defmacro dlet (bindings &body body)
  (loop for (var val) in bindings
        collect var into vars
        collect val into vals
        finally (return `(progv (list ,@vars) (list ,@vals)
                           ,@body))))

(defmacro dset (&rest pairs)
  `(setf ,@(loop for (var val) on pairs by #'cddr
                 collect `(symbol-value ,var)
                 collect val)))

(defmacro dref (variable)
  `(symbol-value ,variable))

Dynamic slots

While meta-classes are not easily composable, it is worth noting that we can mold it better into the language by specifying that slot itself has a dynamic value. This way CLOS aficionados will have a new tool in their arsenal.

The approach we'll take is that a fresh symbol is stored as the value of each instance-allocated slot, and then accessors for the slot value will use these symbols as a dynamic variable. Here are low-level accessors:

;;; Accessing and binding symbols behind the slot. We don't use SLOT-VALUE,
;;; because it will return the _value_ of the dynamic variable, and not the
;;; variable itself.
(defun slot-dvar (object slotd)
  (mop:standard-instance-access
   object (mop:slot-definition-location slotd)))

(defun slot-dvar* (object slot-name)
  (let* ((class (class-of object))
         (slotd (find slot-name (mop:class-slots class)
                      :key #'mop:slot-definition-name)))
    (slot-dvar object slotd)))

(defmacro slot-dlet (bindings &body body)
  `(dlet ,(loop for ((object slot-name) val) in bindings
                 collect `((slot-dvar* ,object ,slot-name) ,val))
     ,@body))

Now we'll define the meta-class. We need that to specialize functions responsible for processing slot definitions and the instance allocation. Notice, that we make use of a kludge to communicate between COMPUTE-EFFECTIVE-SLOT-DEFINITION and EFFECTIVE-SLOT-DEFINITION-CLASS – this is because the latter has no access to the direct slot definitions.

;;; The metaclass CLASS-WITH-DYNAMIC-SLOTS specifies alternative effective slot
;;; definitions for slots with an initarg :dynamic.
(defclass class-with-dynamic-slots (standard-class) ())

;;; Class with dynamic slots may be subclasses of the standard class.
(defmethod mop:validate-superclass ((class class-with-dynamic-slots)
                                    (super standard-class))
  t)

;;; When allocating the instance we initialize all slots to a fresh symbol that
;;; represents the dynamic variable.
(defmethod allocate-instance ((class class-with-dynamic-slots) &rest initargs)
  (declare (ignore initargs))
  (let ((object (call-next-method)))
    (loop for slotd in (mop:class-slots class)
          when (typep slotd 'dynamic-effective-slot) do
            (setf (mop:standard-instance-access
                   object
                   (mop:slot-definition-location slotd))
                  (gensym (string (mop:slot-definition-name slotd)))))
    object))

;;; To improve potential composability of CLASS-WITH-DYNAMIC-SLOTS with other
;;; metaclasses we treat specially only slots that has :DYNAMIC in initargs,
;;; otherwise we call the next method.
(defmethod mop:direct-slot-definition-class
    ((class class-with-dynamic-slots) &rest initargs)
  (loop for (key val) on initargs by #'cddr
        when (eq key :dynamic)
          do (return-from mop:direct-slot-definition-class
               (find-class 'dynamic-direct-slot)))
  (call-next-method))

;;; The metaobject protocol did not specify an elegant way to communicate
;;; between the direct slot definition and the effective slot definition.
;;; Luckily we have dynamic bindings! :-)
(defvar *kludge/mop-deficiency/dynamic-slot-p* nil)
(defmethod mop:compute-effective-slot-definition
    ((class class-with-dynamic-slots)
     name
     direct-slotds)
  (if (typep (first direct-slotds) 'dynamic-direct-slot)
      (let* ((*kludge/mop-deficiency/dynamic-slot-p* t))
        (call-next-method))
      (call-next-method)))

(defmethod mop:effective-slot-definition-class
    ((class class-with-dynamic-slots) &rest initargs)
  (declare (ignore initargs))
  (if *kludge/mop-deficiency/dynamic-slot-p*
      (find-class 'dynamic-effective-slot)
      (call-next-method)))

Finally we define a direct and an effective slot classes, and specialize slot accessors that are invoked by the instance accessors.

;;; There is a considerable boilerplate involving customizing slots.
;;;
;;; - direct slot definition: local to a single defclass form
;;;
;;; - effective slot definition: combination of all direct slots with the same
;;;   name in the class and its superclasses
;;;
(defclass dynamic-direct-slot (mop:standard-direct-slot-definition)
  ((dynamic :initform nil :initarg :dynamic :reader dynamic-slot-p)))

;;; DYNAMIC-EFFECTIVE-SLOT is implemented to return as slot-value values of the
;;; dynamic variable that is stored with the instance.
;;;
;;; It would be nice if we could specify :ALLOCATION :DYNAMIC for the slot, but
;;; then STANDARD-INSTANCE-ACCESS would go belly up. We could make a clever
;;; workaround, but who cares?
(defclass dynamic-effective-slot (mop:standard-effective-slot-definition)
  ())

(defmethod mop:slot-value-using-class
    ((class class-with-dynamic-slots)
     object
     (slotd dynamic-effective-slot))
  (dref (slot-dvar object slotd)))

(defmethod (setf mop:slot-value-using-class)
    (new-value
     (class class-with-dynamic-slots)
     object
     (slotd dynamic-effective-slot))
  (dset (slot-dvar object slotd) new-value))

(defmethod mop:slot-boundp-using-class
  ((class class-with-dynamic-slots)
   object
   (slotd dynamic-effective-slot))
  (boundp (slot-dvar object slotd)))

(defmethod mop:slot-makunbound-using-class
  ((class class-with-dynamic-slots)
   object
   (slotd dynamic-effective-slot))
  (makunbound (slot-dvar object slotd)))

With this, we can finally define a class with slots that have dynamic values. What's more, we may bind them like dynamic variables.

;;; Let there be light.
(defclass window-4 ()
  ((ink :initform 'red :dynamic t :accessor ink)
   (normal :initform 'normal :accessor normal))
  (:metaclass class-with-dynamic-slots))

(let ((object (make-instance 'window-4)))
  (slot-dlet (((object 'ink) 15))
    (print (ink object)))
  (print (ink object)))

ContextL provides a similar solution with dynamic slots, although it provides much more, like layered classes. This example is much more self-contained.

The context

Lately I'm working on the repaint queue for McCLIM. While doing so I've decided to make stream operations thread-safe, so it is possible to draw on the stream and write to it from arbitrary thread asynchronously. The access to the output record history needs to be clearly locked, so that may be solved by the mutex. Graphics state is another story, consider the following functions running from separate threads:

(defun team-red ()
  (with-drawing-options (stream :ink +dark-red+)
    (loop for i from 0 below 50000 do
      (write-string (format nil "XXX: ~5d~%" i) stream))))

(defun team-blue ()
  (with-drawing-options (stream :ink +dark-blue+)
    (loop for i from 0 below 50000 do
      (write-string (format nil "YYY: ~5d~%" i) stream))))

(defun team-pink ()
  (with-drawing-options (stream :ink +deep-pink+)
    (loop for i from 0 below 25000 do
      (case (random 2)
        (0 (draw-rectangle* stream 200 (* i 100) 250 (+ (* i 100) 50)))
        (1 (draw-circle* stream 225 (+ (* i 100) 25) 25))))))

(defun gonow (stream)
  (window-clear stream)
  (time (let ((a (clim-sys:make-process #'team-red))
              (b (clim-sys:make-process #'team-blue))
              (c (clim-sys:make-process #'team-grue)))
          (bt:join-thread a)
          (bt:join-thread b)
          (bt:join-thread c)
          (format stream "done!~%")))  )

Operations like WRITE-STRING and DRAW-RECTANGLE can be implemented by holding a lock over the shared resource without much disruption. The drawing color on the other hand is set outside of the loop, so if we had locked the graphics state with a lock, then these functions would be serialized despite being called from different processes. The solution to this problem is to make graphics context a dynamic slot that is accessed with WITH-DRAWING-OPTIONS.

Summary

I hope that I've convinced you that dynamic variables are cool (I'm sure that majority of readers here are already convinced), and that dynamic slots are even cooler :-). Watch forward to the upcoming McCLIM release!

If you like technical writeups like this, please consider supporting me on Patreon.




re

vindarel: Running my 4th Common Lisp script in production© - you can do it too

Last week I finished a new service written in Common Lisp. It now runs in production© every mornings, and it expands the set of services I offer to clients.

It’s the 4th service of this kind that I developed: - they are not big - but have to be done nonetheless, and the quicker the better (they each amount to 1k to 2k lines of Lisp code), - they are not part of a super advanced domain that requires Common Lisp superpowers - I am the one who benefits from CL during development, - I could have written them in Python - and conversely nothing prevented me from writing them in Common Lisp.

So here lies the goal of this post: illustrate that you don’t need to need a super difficult problem to use Common Lisp. This has been asked many times, directly to me or on social media :)

At the same time, I want to encourage you to write a little something about how you use Common Lisp in the real world. Sharing creates emulation. Do it! If you don’t have a blog you can simply write in a new GitHub repository or in a Gist and come share on /r/lisp. We don’t care. Thanks <3

We’ll briefly see what my scripts do, what libraries I use, how I deploy them, what I did along the way.

Needless to say that I dogfooded my CIEL (beta) meta-library and scripting tool for all those projects.

Table of Contents

Scripts n°4 and 2 - shaping and sending data - when you can write Lisp on the side

My latest script needs to read data from a DB, format what’s necessary according to specifications, and send the result by SFTP.

In this case I read a DB that I own, created by a software that I develop and host. So I could have developed this script in the software itself, right? I could have, but I would have been tied to the main project’s versioning scheme, quirks, and deployment. I rather had to write this script on the side. And since it can be done on the side, it can be done in Common Lisp.

I have to extract products and their data (price, VAT...), aggregate the numbers for each day, write this to a file, according to a specification.

To read the DB, I used cl-dbi. I didn’t format the SQL with SxQL this time like in my web apps (where I use the Mito light ORM), but I wrote SQL directly. I’m spoiled by the Django ORM (which has its idiosyncrasies and shortcomings), so I double checked the different kinds of JOINs and all went well.

I had to group rows by some properties, so it was a great time to use serapeum:assort. I left you an example here: https://dev.to/vindarel/common-lisps-group-by-is-serapeumassort-32ma

Dates have to be handled in different formats. I used local-time of course, and I still greatly appreciate its lispy formatter syntax:

(defun date-yymmddhhnnss (&optional date stream)
  (local-time:format-timestring stream
                                (or date (local-time:now))
                                :format
                                '((:year 4)
                                  (:month 2)
                                  (:day 2)
                                  (:hour 2)
                                  (:min 2)
                                  (:sec 2)
                                  )))

the 2 in (:month 2) is to ensure the month is written with 2 digits.

Once the file is written, I have to send it to a SFTP server, with the client’s codes.

I wrote a profile class to encapsulate the client’s data as well as some functions to read the credentials from either environment variables, the file system, or a lisp variable. I had a top-level profile object for ease of testing, but I made sure that my functions formatting or sending data required a profile parameter.

(defun send-stock (profile &key date) ...)
(defun write-stock (profile filename) ...)

Still nothing surprising, but it’s tempting to only use global parameters for a one-off script. Except the program grows and you pay the mess later.

SFTP

To send the result through SFTP, I had to make a choice. The SFTP command line doesn’t make it possible to give a password as argument (or via an environment variable, etc). So I use lftp (in Debian repositories) that allows to do that. In the end, we format a command like this:

lftp sftp://user:****@host  -e "CD I/; put local-file.name; bye"

You can format the command string and run it with uiop:run-program: no problem, but I took the opportunity to release another utility:

First, you create a profile object. This one-liner reads the credentials from a lispy file:

(defvar profile (make-profile-from-plist (uiop:read-file-form "CREDS.lisp-expr"))

then you define the commands you’ll want to run:

(defvar command (put :cd "I/" :local-filename "data.csv"))
;; #<PUT cd: "I/", filename: "data.csv" {1007153883}>

and finally you call the run method on a profile and a command. Tada.

Deploying

Build a binary the classic way (it’s all on the Cookbook), send it to your server, run it.

(during a testing phase I have deployed “as a script”, from sources, which is a bit quicker to pull changes and try again on the server)

Set up a CRON job.

No Python virtual env to activate in the CRON environment...

Add command line arguments the easy way or with the library of your choice (I like Clingon).

Script n°2 and simple FTP

My script #2 at the time was similar and simpler. I extract the same products but only take their quantities, and I assemble lines like

EXTRACTION STOCK DU 11/04/2008
....978202019116600010000001387
....978270730656200040000000991

For this service, we have to send the file to a simple FTP server.

We have a pure Lisp library for FTP (and not SFTP) which works very well, cl-ftp.

It’s a typical example of an old library that didn’t receive any update in years and so that looks abandoned, that has seldom documentation but whose usage is easy to infer, and that does its job as requested.

For example we do this to send a file:

(ftp:with-ftp-connection (conn :hostname hostname
                                   :username username
                                   :password password
                                   :passive-ftp-p t)
      (ftp:store-file conn local-filename filename))

I left you notes about cl-ftp and my SFTP wrapper here:

Scripts n°3 and n°1 - specialized web apps

A recent web app that I’m testing with a couple clients extends an existing stock management system.

This one also was done in order to avoid a Python monolith. I still needed additions in the Python main software, but this little app can be independent and grow on its own. The app maintains its state and communicates it with a REST API.

 

It gives a web interface to their clients (so my clients’ clients, but not all of them, only the institutional) so that they can:

  • search for products
  • add them in shopping carts
  • validate the cart, which sends the data to the main software and notifies the owner, who will work on them.

The peculiarities of this app are that:

  • there is no user login, we use unique URLs with UUIDs in the form: http://command.client.com/admin-E9DFOO82-R2D2-007/list?id=1
  • I need a bit of file persistence but I didn’t want the rigidity of a database so I am using the clache library. Here also, not a great activity, but it works©. I persist lists and hash-tables. Now that the needs grow and the original scope doesn’t cut it any more, I wonder how long I’ll survive without a DB. Only for its short SQL queries VS lisp code to filter data.

I deploy a self-contained binary: code + html templates in the same binary (+ the implementation, the web server, the debugger...), with Systemd.

I wrote more on how to ship a standalone binary with templates and static assets with Djula templates here:

I can connect to the running app with a Swank server to check and set parameters, which is super helpful and harmless.

It is possible to reload the whole app from within itself and I did it with no hiccups for a couple years, but it isn’t necessary the most reliable, easiest to set up and fastest method. You can do it, but nobody forces you to do this because you are running CL in production. You can use the industry’s boring and best practices too. Common Lisp doesn’t inforce a “big ball of mud” approach. Develop locally, use Git, use a CI, deploy a binary...

Every thing that I learned I documented it along the way in the Cookbook ;)

Another app that I’ll mention but about which I also wrote earlier is my first web app. This one is open-source. It still runs :)

 

In this project I had my friend and colleague contribute five lines of Lisp code to add a theme switcher in the backend that would help him do the frontend. He had never written a line of Lisp before. Of course, he did so by looking at my existing code to learn the existing functions at hand, and he could do it because the project was easy to install and run.

(defun get-template(template &optional (theme *theme*))
  "Loads template from the base templates directory or from the given theme templates directory if it exists."
  (if (and (str:non-blank-string-p theme)
           (probe-file (asdf:system-relative-pathname "abstock" (str:concat "src/templates/themes/" theme "/" template))))
      ;; then
      (str:concat "themes/" theme "/" template)
      ;; else :D
      template))

He had to annotate the if branches :] This passed the code review.

Lasting words

The 5th script/app is already on the way, and the next ones are awaiting that I open their .docx specification files. This one was a bit harder but the Lisp side was done sucessfully with the efficient collaboration of another freelance lisper (Kevin to not name him).

All those tasks (read a DB, transform data...) are very mundane.

They are everywhere. They don’t always need supercharged web framework or integrations.

You have plenty of opportunities to make yourself a favor, and use Common Lisp in the wild. Not counting the super-advanced domains where Lisp excels at ;)


Links

I have done some preliminary Common Lisp exploration prior to this course but had a lot of questions regarding practical use and development workflows. This course was amazing for this! I learned a lot of useful techniques for actually writing the code in Emacs, as well as conversational explanations of concepts that had previously confused me in text-heavy resources. Please keep up the good work and continue with this line of topics, it is well worth the price! [Preston, October of 2024]




re

TurtleWare: Dynamic Vars - The Empire Strikes Back

Table of Contents

  1. Thread Local storage exhausted
  2. The layer of indirection
  3. I can fix her
  4. Let's write some tests!
  5. Summary

Thread Local storage exhausted

In the last post I've described a technique to use dynamic variables by value instead of the name by utilizing the operator PROGV. Apparently it works fine on all Common Lisp implementations I've tried except from SBCL, where the number of thread local variables is by default limited to something below 4000. To add salt to the injury, these variables are not garbage collected.

Try the following code to crash into LDB:

(defun foo ()
  (loop for i from 0 below 4096 do
    (when (zerop (mod i 100))
      (print i))
    (progv (list (gensym)) (list 42)
      (values))))
(foo)

This renders our new technique not very practical given SBCL popularity. We need to either abandon the idea or come up with a workaround.

The layer of indirection

Luckily for us we've already introduced a layer of indirection. Operators to access dynamic variables are called DLET, DSET and DREF. This means, that it is enough to provide a kludge implementation for SBCL with minimal changes to the remaining code.

The old code works the same as previously except that instead of SYMBOL-VALUE we use the accessor DYNAMIC-VARIABLE-VALUE, and the old call to PROGV is now DYNAMIC-VARIABLE-PROGV. Moreover DYNAMIC-EFFECTIVE-SLOT used functions BOUNDP and MAKUNBOUND, so we replace these with DYNAMIC-VARIABLE-BOUND-P and DYNAMIC-VARIABLE-MAKUNBOUND. To abstract away things further we also introduce the constructor MAKE-DYNAMIC-VARIABLE

(defpackage "EU.TURTLEWARE.BLOG/DLET"
  (:local-nicknames ("MOP" #+closer-mop "C2MOP"
                           #+(and (not closer-mop) ecl) "MOP"
                           #+(and (not closer-mop) ccl) "CCL"
                           #+(and (not closer-mop) sbcl) "SB-MOP"))
  (:use "CL"))
(in-package "EU.TURTLEWARE.BLOG/DLET")

(eval-when (:compile-toplevel :execute :load-toplevel)
  (unless (member :bordeaux-threads *features*)
    (error "Please load BORDEAUX-THREADS."))
  (when (member :sbcl *features*)
    (unless (member :fake-progv-kludge *features*)
      (format t "~&;; Using FAKE-PROGV-KLUDGE for SBCL.~%")
      (push :fake-progv-kludge *features*))))

(defmacro dlet (bindings &body body)
  (flet ((pred (binding)
           (and (listp binding) (= 2 (length binding)))))
    (unless (every #'pred bindings)
      (error "DLET: bindings must be lists of two values.~%~
                Invalid bindings:~%~{ ~s~%~}" (remove-if #'pred bindings))))
  (loop for (var val) in bindings
        collect var into vars
        collect val into vals
        finally (return `(dynamic-variable-progv (list ,@vars) (list ,@vals)
                           ,@body))))

(defmacro dset (&rest pairs)
  `(setf ,@(loop for (var val) on pairs by #'cddr
                 collect `(dref ,var)
                 collect val)))

(defmacro dref (variable)
  `(dynamic-variable-value ,variable))

;;; ...

(defmethod mop:slot-boundp-using-class
    ((class standard-class)
     object
     (slotd dynamic-effective-slot))
  (dynamic-variable-bound-p (slot-dvar object slotd)))

(defmethod mop:slot-makunbound-using-class
    ((class standard-class)
     object
     (slotd dynamic-effective-slot))
  (dynamic-variable-makunbound (slot-dvar object slotd)))

With these in place we can change the portable implementation to conform.

#-fake-progv-kludge
(progn
  (defun make-dynamic-variable ()
    (gensym))

  (defun dynamic-variable-value (variable)
    (symbol-value variable))

  (defun (setf dynamic-variable-value) (value variable)
    (setf (symbol-value variable) value))

  (defun dynamic-variable-bound-p (variable)
    (boundp variable))

  (defun dynamic-variable-makunbound (variable)
    (makunbound variable))

  (defmacro dynamic-variable-progv (vars vals &body body)
    `(progv ,vars ,vals ,@body)))

I can fix her

The implementation for SBCL will mediate access to the dynamic variable value with a synchronized hash table with weak keys. The current process is the key of the hash table and the list of bindings is the value of the hash table. For compatibility between implementations the top level value of the symbol will be shared.

The variable +FAKE-UNBOUND+ is the marker that signifies, that the variable has no value. When the list of bindings is EQ to +CELL-UNBOUND+, then it means that we should use the global value. We add new bindings by pushing to it.

#+fake-progv-kludge
(progn
  (defvar +fake-unbound+ 'unbound)
  (defvar +cell-unbound+ '(no-binding))

  (defclass dynamic-variable ()
    ((tls-table
      :initform (make-hash-table :synchronized t :weakness :key)
      :reader dynamic-variable-tls-table)
     (top-value
      :initform +fake-unbound+
      :accessor dynamic-variable-top-value)))

  (defun make-dynamic-variable ()
    (make-instance 'dynamic-variable))

  (defun dynamic-variable-bindings (dvar)
    (let ((process (bt:current-thread))
          (tls-table (dynamic-variable-tls-table dvar)))
      (gethash process tls-table +cell-unbound+)))

  (defun (setf dynamic-variable-bindings) (value dvar)
    (let ((process (bt:current-thread))
          (tls-table (dynamic-variable-tls-table dvar)))
      (setf (gethash process tls-table +cell-unbound+) value))))

We define two readers for the variable value - one that simply reads the value, and the other that signals an error if the variable is unbound. Writer for its value either replaces the current binding, or if the value cell is unbound, then we modify the top-level symbol value. We use the value +FAKE-UNBOUND+ to check whether the variable is bound and to make it unbound.

#+fake-progv-kludge
(progn
  (defun %dynamic-variable-value (dvar)
    (let ((tls-binds (dynamic-variable-bindings dvar)))
      (if (eq tls-binds +cell-unbound+)
          (dynamic-variable-top-value dvar)
          (car tls-binds))))

  (defun dynamic-variable-value (dvar)
    (let ((tls-value (%dynamic-variable-value dvar)))
      (when (eq tls-value +fake-unbound+)
        (error 'unbound-variable :name "(unnamed)"))
      tls-value))

  (defun (setf dynamic-variable-value) (value dvar)
    (let ((tls-binds (dynamic-variable-bindings dvar)))
      (if (eq tls-binds +cell-unbound+)
          (setf (dynamic-variable-top-value dvar) value)
          (setf (car tls-binds) value))))

  (defun dynamic-variable-bound-p (dvar)
    (not (eq +fake-unbound+ (%dynamic-variable-value dvar))))

  (defun dynamic-variable-makunbound (dvar)
    (setf (dynamic-variable-value dvar) +fake-unbound+)))

Finally we define the operator to dynamically bind variables that behaves similar to PROGV. Note that we PUSH and POP from the thread-local hash table DYNAMIC-VARIABLE-BINDINGS, so no synchronization is necessary.

#+fake-progv-kludge
(defmacro dynamic-variable-progv (vars vals &body body)
  (let ((svars (gensym))
        (svals (gensym))
        (var (gensym))
        (val (gensym)))
    `(let ((,svars ,vars))
       (loop for ,svals = ,vals then (rest ,svals)
             for ,var in ,svars
             for ,val = (if ,svals (car ,svals) +fake-unbound+)
             do (push ,val (dynamic-variable-bindings ,var)))
       (unwind-protect (progn ,@body)
         (loop for ,var in ,svars
               do (pop (dynamic-variable-bindings ,var)))))))

Let's write some tests!

But of course, we are going to also write a test framework. It's short, I promise. As a bonus point the API is compatibile with fiveam, so it is possible to drop tests as is in the appropriate test suite.

(defvar *all-tests* '())

(defun run-tests ()
  (dolist (test (reverse *all-tests*))
    (format *debug-io* "Test ~a... " test)
    (handler-case (funcall test)
      (serious-condition (c)
        (format *debug-io* "Failed: ~a~%" c))
      (:no-error (&rest args)
        (declare (ignore args))
        (format *debug-io* "Passed.~%")))))

(defmacro test (name &body body)
  `(progn
     (pushnew ',name *all-tests*)
     (defun ,name () ,@body)))

(defmacro is (form)
  `(assert ,form))

(defmacro pass ())

(defmacro signals (condition form)
  `(is (block nil
         (handler-case ,form
           (,condition () (return t)))
         nil)))

(defmacro finishes (form)
  `(is (handler-case ,form
         (serious-condition (c)
           (declare (ignore c))
           nil)
         (:no-error (&rest args)
           (declare (ignore args))
           t))))

Now let's get to tests. First we'll test our metaclass:

(defclass dynamic-let.test-class ()
  ((slot1 :initarg :slot1 :dynamic nil :accessor slot1)
   (slot2 :initarg :slot2 :dynamic t   :accessor slot2)
   (slot3 :initarg :slot3              :accessor slot3))
  (:metaclass class-with-dynamic-slots))

(defparameter *dynamic-let.test-instance-1*
  (make-instance 'dynamic-let.test-class
                 :slot1 :a :slot2 :b :slot3 :c))

(defparameter *dynamic-let.test-instance-2*
  (make-instance 'dynamic-let.test-class
                 :slot1 :x :slot2 :y :slot3 :z))

(test dynamic-let.1
  (let ((o1 *dynamic-let.test-instance-1*)
        (o2 *dynamic-let.test-instance-2*))
    (with-slots (slot1 slot2 slot3) o1
      (is (eq :a slot1))
      (is (eq :b slot2))
      (is (eq :c slot3)))
    (with-slots (slot1 slot2 slot3) o2
      (is (eq :x slot1))
      (is (eq :y slot2))
      (is (eq :z slot3)))))

(test dynamic-let.2
  (let ((o1 *dynamic-let.test-instance-1*)
        (o2 *dynamic-let.test-instance-2*))
    (signals error (slot-dlet (((o1 'slot1) 1)) nil))
    (slot-dlet (((o1 'slot2) :k))
      (is (eq :k (slot-value o1 'slot2)))
      (is (eq :y (slot-value o2 'slot2))))))

(test dynamic-let.3
  (let ((o1 *dynamic-let.test-instance-1*)
        (exit nil)
        (fail nil))
    (flet ((make-runner (values)
             (lambda ()
               (slot-dlet (((o1 'slot2) :start))
                 (let ((value (slot2 o1)))
                   (unless (eq value :start)
                     (setf fail value)))
                 (loop until (eq exit t) do
                   (setf (slot2 o1) (elt values (random (length values))))
                   (let ((value (slot2 o1)))
                     (unless (member value values)
                       (setf fail value)
                       (setf exit t))))))))
      (let ((r1 (bt:make-thread (make-runner '(:k1 :k2))))
            (r2 (bt:make-thread (make-runner '(:k3 :k4))))
            (r3 (bt:make-thread (make-runner '(:k5 :k6)))))
        (sleep .1)
        (setf exit t)
        (map nil #'bt:join-thread (list r1 r2 r3))
        (is (eq (slot2 o1) :b))
        (is (null fail))))))

Then let's test the dynamic variable itself:

(test dynamic-let.4
  "Test basic dvar operators."
  (let ((dvar (make-dynamic-variable)))
    (is (eql 42 (dset dvar 42)))
    (is (eql 42 (dref dvar)))
    (ignore-errors
     (dlet ((dvar :x))
       (is (eql :x (dref dvar)))
       (error "foo")))
    (is (eql 42 (dref dvar)))))

(test dynamic-let.5
  "Test bound-p operator."
  (let ((dvar (make-dynamic-variable)))
    (is (not (dynamic-variable-bound-p dvar)))
    (dset dvar 15)
    (is (dynamic-variable-bound-p dvar))
    (dynamic-variable-makunbound dvar)
    (is (not (dynamic-variable-bound-p dvar)))))

(test dynamic-let.6
  "Test makunbound operator."
  (let ((dvar (make-dynamic-variable)))
    (dset dvar t)
    (is (dynamic-variable-bound-p dvar))
    (finishes (dynamic-variable-makunbound dvar))
    (is (not (dynamic-variable-bound-p dvar)))))

(test dynamic-let.7
  "Test locally bound-p operator."
  (let ((dvar (make-dynamic-variable)))
    (is (not (dynamic-variable-bound-p dvar)))
    (dlet ((dvar 15))
      (is (dynamic-variable-bound-p dvar)))
    (is (not (dynamic-variable-bound-p dvar)))))

(test dynamic-let.8
  "Test locally unbound-p operator."
  (let ((dvar (make-dynamic-variable)))
    (dset dvar t)
    (is (dynamic-variable-bound-p dvar))
    (dlet ((dvar nil))
      (is (dynamic-variable-bound-p dvar))
      (finishes (dynamic-variable-makunbound dvar))
      (is (not (dynamic-variable-bound-p dvar))))
    (is (dynamic-variable-bound-p dvar))))

(test dynamic-let.9
  "Stress test the implementation (see :FAKE-PROGV-KLUDGE)."
  (finishes                              ; at the same time
    (let ((dvars (loop repeat 4096 collect (make-dynamic-variable))))
      ;; ensure tls variable
      (loop for v in dvars do
        (dlet ((v 1))))
      (loop for i from 0 below 4096
            for r = (random 4096)
            for v1 in dvars
            for v2 = (elt dvars r) do
              (when (zerop (mod i 64))
                (pass))
              (dlet ((v1 42)
                     (v2 43))
                (values))))))

(test dynamic-let.0
  "Stress test the implementation (see :FAKE-PROGV-KLUDGE)."
  (finishes                             ; can be gc-ed
    (loop for i from 0 below 4096 do
      (when (zerop (mod i 64))
        (pass))
      (dlet (((make-dynamic-variable) 42))
        (values)))))

All that is left is to test both dynamic variable implementations:

BLOG/DLET> (lisp-implementation-type)
"ECL"
BLOG/DLET> (run-tests)
Test DYNAMIC-LET.1... Passed.
Test DYNAMIC-LET.2... Passed.
Test DYNAMIC-LET.3... Passed.
Test DYNAMIC-LET.4... Passed.
Test DYNAMIC-LET.5... Passed.
Test DYNAMIC-LET.6... Passed.
Test DYNAMIC-LET.7... Passed.
Test DYNAMIC-LET.8... Passed.
Test DYNAMIC-LET.9... Passed.
Test DYNAMIC-LET.0... Passed.
NIL

And with the kludge:

BLOG/DLET> (lisp-implementation-type)
"SBCL"
BLOG/DLET> (run-tests)
Test DYNAMIC-LET.1... Passed.
Test DYNAMIC-LET.2... Passed.
Test DYNAMIC-LET.3... Passed.
Test DYNAMIC-LET.4... Passed.
Test DYNAMIC-LET.5... Passed.
Test DYNAMIC-LET.6... Passed.
Test DYNAMIC-LET.7... Passed.
Test DYNAMIC-LET.8... Passed.
Test DYNAMIC-LET.9... Passed.
Test DYNAMIC-LET.0... Passed.
NIL

Summary

In this post we've made our implementation to work on SBCL even when there are more than a few thousand dynamic variables. We've also added a simple test suite that checks the basic behavior.

As it often happens, after achieving some goal we get greedy and achieve more. That's the case here as well. In the next (and the last) post in this series I'll explore the idea of adding truly thread-local variables without a shared global value. This will be useful for lazily creating context on threads that are outside of our control. We'll also generalize the implementation so it is possible to subclass and implement ones own flavor of a dynamic variable.




re

TurtleWare: Dynamic Vars - Return of the Jedi

Table of Contents

  1. The protocol
  2. Control operators
  3. Synchronized hash tables with weakness
  4. First-class dynamic variables
    1. STANDARD-DYNAMIC-VARIABLE
    2. SURROGATE-DYNAMIC-VARIABLE
  5. Thread-local variables
    1. The protocol
    2. The implementation
  6. Thread-local slots
  7. What can we use it for?

In the previous two posts I've presented an implementation of first-class dynamic variables using PROGV and a surrogate implementation for SBCL.

Now we will double down on this idea and make the protocol extensible. Finally we'll implement a specialized version of dynamic variables where even the top level value of the variable is thread-local.

The protocol

Previously we've defined operators as either macros or functions. Different implementations were protected by the feature flag and symbols collided. Now we will introduce the protocol composed of a common superclass and functions that are specialized by particular implementations.

Most notably we will introduce a new operator CALL-WITH-DYNAMIC-VARIABLE that is responsible for establishing a single binding. Thanks to that it will be possible to mix dynamic variables of different types within a single DLET statement.

(defclass dynamic-variable () ())

(defgeneric dynamic-variable-bindings (dvar))
(defgeneric dynamic-variable-value (dvar))
(defgeneric (setf dynamic-variable-value) (value dvar))
(defgeneric dynamic-variable-bound-p (dvar))
(defgeneric dynamic-variable-makunbound (dvar))
(defgeneric call-with-dynamic-variable (cont dvar &optional value))

Moreover we'll define a constructor that is specializable by a key. This design will allow us to refer to the dynamic variable class by using a shorter name. We will also define the standard class to be used and an matching constructor.

(defparameter *default-dynamic-variable-class*
  #-fake-progv-kludge 'standard-dynamic-variable
  #+fake-progv-kludge 'surrogate-dynamic-variable)

(defgeneric make-dynamic-variable-using-key (key &rest initargs)
  (:method (class &rest initargs)
    (apply #'make-instance class initargs))
  (:method ((class (eql t)) &rest initargs)
    (apply #'make-instance *default-dynamic-variable-class* initargs))
  (:method ((class null) &rest initargs)
    (declare (ignore class initargs))
    (error "Making a dynamic variable that is not, huh?")))

(defun make-dynamic-variable (&rest initargs)
  (apply #'make-dynamic-variable-using-key t initargs))

Control operators

Control operators are the same as previously, that is a set of four macros that consume the protocol specified above. Note that DYNAMIC-VARIABLE-PROGV expands to a recursive call where each binding is processed separately.

(defmacro dlet (bindings &body body)
  (flet ((pred (binding)
           (and (listp binding) (= 2 (length binding)))))
    (unless (every #'pred bindings)
      (error "DLET: bindings must be lists of two values.~%~
              Invalid bindings:~%~{ ~s~%~}" (remove-if #'pred bindings))))
  (loop for (var val) in bindings
        collect var into vars
        collect val into vals
        finally (return `(dynamic-variable-progv (list ,@vars) (list ,@vals)
                           ,@body))))

(defmacro dset (&rest pairs)
  `(setf ,@(loop for (var val) on pairs by #'cddr
                 collect `(dref ,var)
                 collect val)))

(defmacro dref (variable)
  `(dynamic-variable-value ,variable))

(defun call-with-dynamic-variable-progv (cont vars vals)
  (flet ((thunk ()
           (if vals
               (call-with-dynamic-variable cont (car vars) (car vals))
               (call-with-dynamic-variable cont (car vars)))))
    (if vars
        (call-with-dynamic-variable-progv #'thunk (cdr vars) (cdr vals))
        (funcall cont))))

(defmacro dynamic-variable-progv (vars vals &body body)
  (let ((cont (gensym)))
    `(flet ((,cont () ,@body))
       (call-with-dynamic-variable-progv (function ,cont) ,vars ,vals))))

Synchronized hash tables with weakness

Previously we've used SBCL-specific options to define a synchronized hash table with weak keys. This won't do anymore, because we will need a similar object to implement the thread-local storage for top level values.

trivial-garbage is a portability layer that allows to define hash tables with a specified weakness, but it does not provide an argument that would abstract away synchronization. We will ensure thread-safety with locks instead.

(defclass tls-table ()
  ((table :initform (trivial-garbage:make-weak-hash-table
                     :test #'eq :weakness :key))
   (lock :initform (bt:make-lock))))

(defun make-tls-table ()
  (make-instance 'tls-table))

(defmacro with-tls-table ((var self) &body body)
  (let ((obj (gensym)))
    `(let* ((,obj ,self)
            (,var (slot-value ,obj 'table)))
       (bt:with-lock-held ((slot-value ,obj 'lock)) ,@body))))

First-class dynamic variables

STANDARD-DYNAMIC-VARIABLE

Previously in the default implementation we've represented dynamic variables with a symbol. The new implementation is similar except that the symbol is read from a STANDARD-OBJECT that represents the variable. This also enables us to specialize the function CALL-WITH-DYNAMIC-VARIABLE:

(defclass standard-dynamic-variable (dynamic-variable)
  ((symbol :initform (gensym) :accessor dynamic-variable-bindings)))

(defmethod dynamic-variable-value ((dvar standard-dynamic-variable))
  (symbol-value (dynamic-variable-bindings dvar)))

(defmethod (setf dynamic-variable-value) (value (dvar standard-dynamic-variable))
  (setf (symbol-value (dynamic-variable-bindings dvar)) value))

(defmethod dynamic-variable-bound-p ((dvar standard-dynamic-variable))
  (boundp (dynamic-variable-bindings dvar)))

(defmethod dynamic-variable-makunbound ((dvar standard-dynamic-variable))
  (makunbound (dynamic-variable-bindings dvar)))

(defmethod call-with-dynamic-variable (cont (dvar standard-dynamic-variable)
                                       &optional (val nil val-p))
  (progv (list (dynamic-variable-bindings dvar)) (if val-p (list val) ())
    (funcall cont)))

SURROGATE-DYNAMIC-VARIABLE

The implementation of the SURROGATE-DYNAMIC-VARIABLE is almost the same as previously. The only difference is that we use the previously defined indirection to safely work with hash tables. Also note, that we are not add the feature condition - both classes is always created.

(defvar +fake-unbound+ 'unbound)
(defvar +cell-unbound+ '(no-binding))

(defclass surrogate-dynamic-variable (dynamic-variable)
  ((tls-table
    :initform (make-tls-table)
    :reader dynamic-variable-tls-table)
   (top-value
    :initform +fake-unbound+
    :accessor dynamic-variable-top-value)))

(defmethod dynamic-variable-bindings ((dvar surrogate-dynamic-variable))
  (let ((process (bt:current-thread)))
    (with-tls-table (tls-table (dynamic-variable-tls-table dvar))
      (gethash process tls-table +cell-unbound+))))

(defmethod (setf dynamic-variable-bindings) (value (dvar surrogate-dynamic-variable))
  (let ((process (bt:current-thread)))
    (with-tls-table (tls-table (dynamic-variable-tls-table dvar))
      (setf (gethash process tls-table) value))))

(defun %dynamic-variable-value (dvar)
  (let ((tls-binds (dynamic-variable-bindings dvar)))
    (if (eq tls-binds +cell-unbound+)
        (dynamic-variable-top-value dvar)
        (car tls-binds))))

(defmethod dynamic-variable-value ((dvar surrogate-dynamic-variable))
  (let ((tls-value (%dynamic-variable-value dvar)))
    (when (eq tls-value +fake-unbound+)
      (error 'unbound-variable :name "(unnamed)"))
    tls-value))

(defmethod (setf dynamic-variable-value) (value (dvar surrogate-dynamic-variable))
  (let ((tls-binds (dynamic-variable-bindings dvar)))
    (if (eq tls-binds +cell-unbound+)
        (setf (dynamic-variable-top-value dvar) value)
        (setf (car tls-binds) value))))

(defmethod dynamic-variable-bound-p ((dvar surrogate-dynamic-variable))
  (not (eq +fake-unbound+ (%dynamic-variable-value dvar))))

(defmethod dynamic-variable-makunbound ((dvar surrogate-dynamic-variable))
  (setf (dynamic-variable-value dvar) +fake-unbound+))


;;; Apparently CCL likes to drop^Helide some writes and that corrupts bindings
;;; table. Let's ensure that the value is volatile.
#+ccl (defvar *ccl-ensure-volatile* nil)
(defmethod call-with-dynamic-variable (cont (dvar surrogate-dynamic-variable)
                                       &optional (val +fake-unbound+))
  (push val (dynamic-variable-bindings dvar))
  (let (#+ccl (*ccl-ensure-volatile* (dynamic-variable-bindings dvar)))
    (unwind-protect (funcall cont)
      (pop (dynamic-variable-bindings dvar)))))

Thread-local variables

We've refactored the previous code to be extensible. Now we can use metaobjects from the previous post without change. We can also test both implementations in the same process interchangeably by customizing the default class parameter.

It is the time now to have some fun and extend dynamic variables into variables with top value not shared between different threads. This will enable ultimate thread safety. With our new protocol the implementation is trivial!

The protocol

First we will define the protocol class. THREAD-LOCAL-VARIABLE is a variant of a DYNAMIC-VARIABLE with thread-local top values.

We specify initialization arguments :INITVAL and :INITFUN that will be used to assign the top value of a binding. The difference is that INITVAL specifies a single value, while INITFUN can produce an unique object on each invocation. INITARG takes a precedence over INTIFUN, and if neither is supplied, then a variable is unbound.

We include the constructor that builds on MAKE-DYNAMIC-VARIABLE-USING-KEY, and macros corresponding to DEFVAR and DEFPARAMETER. Note that they expand to :INITFUN - this assures that the initialization form is re-evaluated for each new thread where the variable is used.

(defclass thread-local-variable (dynamic-variable) ())

(defmethod initialize-instance :after
    ((self thread-local-variable) &key initfun initval)
  (declare (ignore self initfun initval)))

(defparameter *default-thread-local-variable-class*
  #-fake-progv-kludge 'standard-thread-local-variable
  #+fake-progv-kludge 'surrogate-thread-local-variable)

(defun make-thread-local-variable (&rest initargs)
  (apply #'make-dynamic-variable-using-key
         *default-thread-local-variable-class* initargs))

(defmacro create-tls-variable (&optional (form nil fp) &rest initargs)
  `(make-thread-local-variable 
    ,@(when fp `(:initfun (lambda () ,form)))
    ,@initargs))

(defmacro define-tls-variable (name &rest initform-and-initargs)
  `(defvar ,name (create-tls-variable ,@initform-and-initargs)))

(defmacro define-tls-parameter (name &rest initform-and-initargs)
  `(defparameter ,name (create-tls-variable ,@initform-and-initargs)))

Perhaps it is a good time to introduce a new convention for tls variable names. I think that surrounding names with the minus sign is a nice idea, because it signifies, that it is something less than a global value. For example:

DYNAMIC-VARS> (define-tls-variable -context- 
                  (progn
                    (print "Initializing context!")
                    (list :context)))
-CONTEXT-
DYNAMIC-VARS> -context-
#<a EU.TURTLEWARE.DYNAMIC-VARS::STANDARD-THREAD-LOCAL-VARIABLE 0x7f7636c08640>
DYNAMIC-VARS> (dref -context-)

"Initializing context!" 
(:CONTEXT)
DYNAMIC-VARS> (dref -context-)
(:CONTEXT)
DYNAMIC-VARS> (dset -context- :the-new-value)

:THE-NEW-VALUE
DYNAMIC-VARS> (dref -context-)
:THE-NEW-VALUE
DYNAMIC-VARS> (bt:make-thread
               (lambda ()
                 (print "Let's read it!")
                 (print (dref -context-))))
#<process "Anonymous thread" 0x7f7637a26cc0>

"Let's read it!" 
"Initializing context!" 
(:CONTEXT) 
DYNAMIC-VARS> (dref -context-)
:THE-NEW-VALUE

The implementation

You might have noticed the inconspicuous operator DYNAMIC-VARIABLE-BINDINGS that is part of the protocol. It returns an opaque object that represents values of the dynamic variable in the current context:

  • for STANDARD-DYNAMIC-VARIABLE it is a symbol
  • for SURROGATE-DYNAMIC-VARIABLE it is a thread-local list of bindings

In any case all other operators first take this object and then use it to read, write or bind the value. The gist of the tls variables implementation is to always return an object that is local to the thread. To store these objects we will use the tls-table we've defined earlier.

(defclass thread-local-variable-mixin (dynamic-variable)
  ((tls-table
    :initform (make-tls-table)
    :reader dynamic-variable-tls-table)
   (tls-initfun
    :initarg :initfun
    :initform nil
    :accessor thread-local-variable-initfun)
   (tls-initval
    :initarg :initval
    :initform +fake-unbound+
    :accessor thread-local-variable-initval)))

For the class STANDARD-THREAD-LOCAL-VARIABLE we will simply return a different symbol depending on the thread:

(defclass standard-thread-local-variable (thread-local-variable-mixin
                                         thread-local-variable
                                         standard-dynamic-variable)
  ())

(defmethod dynamic-variable-bindings ((tvar standard-thread-local-variable))
  (flet ((make-new-tls-bindings ()
           (let ((symbol (gensym))
                 (initval (thread-local-variable-initval tvar))
                 (initfun (thread-local-variable-initfun tvar)))
             (cond
               ((not (eq +fake-unbound+ initval))
                (setf (symbol-value symbol) initval))
               ((not (null initfun))
                (setf (symbol-value symbol) (funcall initfun))))
             symbol)))
    (let ((key (bt:current-thread)))
      (with-tls-table (tls-table (dynamic-variable-tls-table tvar))
        (or (gethash key tls-table)
            (setf (gethash key tls-table)
                  (make-new-tls-bindings)))))))

And for the class SURROGATE-THREAD-LOCAL-VARIABLE the only difference from the SURROGATE-DYNAMIC-VARIABLE implementation is to cons a new list as the initial value (even when it is unbound) to ensure it is not EQ to +CELL-UNBOUND+.

(defclass surrogate-thread-local-variable (thread-local-variable-mixin
                                          thread-local-variable
                                          surrogate-dynamic-variable)
  ())

(defmethod dynamic-variable-bindings ((tvar surrogate-thread-local-variable))
  (flet ((make-new-tls-bindings ()
           (let ((initval (thread-local-variable-initval tvar))
                 (initfun (thread-local-variable-initfun tvar)))
             (cond
               ((not (eq +fake-unbound+ initval))
                (list initval))
               ((not (null initfun))
                (list (funcall initfun)))
               (t
                (list +fake-unbound+))))))
    (let ((key (bt:current-thread)))
      (with-tls-table (tls-table (dynamic-variable-tls-table tvar))
        (or (gethash key tls-table)
            (setf (gethash key tls-table)
                  (make-new-tls-bindings)))))))

That's all, now we have two implementations of thread-local variables. Ramifications are similar as with "ordinary" dynamic variables - the standard implementation is not advised for SBCL, because it will crash in LDB.

Thread-local slots

First we are going to allow to defined dynamic variable types with an abbreviated names. This will enable us to specify in the slot definition that type, for example (MY-SLOT :DYNAMIC :TLS :INITFORM 34)

;;; Examples how to add shorthand type names for the dynamic slots:

(defmethod make-dynamic-variable-using-key ((key (eql :tls)) &rest initargs)
  (apply #'make-dynamic-variable-using-key
         *default-thread-local-variable-class* initargs))

(defmethod make-dynamic-variable-using-key ((key (eql :normal-tls)) &rest initargs)
  (apply #'make-dynamic-variable-using-key
         'standard-thread-local-variable initargs))

(defmethod make-dynamic-variable-using-key ((key (eql :kludge-tls)) &rest initargs)
  (apply #'make-dynamic-variable-using-key
         'surrogate-thread-local-variable initargs))

;;; For *DEFAULT-DYNAMIC-VARIABLE* specify :DYNAMIC T.

(defmethod make-dynamic-variable-using-key ((key (eql :normal-dyn)) &rest initargs)
  (apply #'make-dynamic-variable-using-key
         'standard-dynamic-variable initargs))

(defmethod make-dynamic-variable-using-key ((key (eql :kludge-dyn)) &rest initargs)
  (apply #'make-dynamic-variable-using-key
         'surrogate-dynamic-variable initargs))

In order to do that, we need to remember he value of the argument :DYNAMIC. We will read it with DYNAMIC-VARIABLE-TYPE and that value will be available in both direct and the effective slot:

;;; Slot definitions
;;; There is a considerable boilerplate involving customizing slots.
;;;
;;; - direct slot definition: local to a single defclass form
;;;
;;; - effective slot definition: combination of all direct slots with the same
;;;   name in the class and its superclasses
;;;
(defclass dynamic-direct-slot (mop:standard-direct-slot-definition)
  ((dynamic :initform nil :initarg :dynamic :reader dynamic-variable-type)))

;;; The metaobject protocol did not specify an elegant way to communicate
;;; between the direct slot definition and the effective slot definition.
;;; Luckily we have dynamic bindings! :-)
(defvar *kludge/mop-deficiency/dynamic-variable-type* nil)

;;; DYNAMIC-EFFECTIVE-SLOT is implemented to return as slot-value values of the
;;; dynamic variable that is stored with the instance.
;;;
;;; It would be nice if we could specify :ALLOCATION :DYNAMIC for the slot, but
;;; then STANDARD-INSTANCE-ACCESS would go belly up. We could make a clever
;;; workaround, but who cares?
(defclass dynamic-effective-slot (mop:standard-effective-slot-definition)
  ((dynamic :initform *kludge/mop-deficiency/dynamic-variable-type*
            :reader dynamic-variable-type)))

Moreover we specialize the function MAKE-DYNAMIC-VARIABLE-USING-KEY to the effective slot class. The initargs in this method are meant for the instance. When the dynamic variable is created, we check whether it is a thread-local variable and initialize its INITVAL and INITFUN to values derived from INITARGS, MOP:SLOT-DEFINITION-INITARGS and MOP:SLOT-DEFINITION-INITFUN:

(defmethod make-dynamic-variable-using-key
    ((key dynamic-effective-slot) &rest initargs)
  (let* ((dvar-type (dynamic-variable-type key))
         (dvar (make-dynamic-variable-using-key dvar-type)))
    (when (typep dvar 'thread-local-variable)
      (loop with slot-initargs = (mop:slot-definition-initargs key)
            for (key val) on initargs by #'cddr
            when (member key slot-initargs) do
              (setf (thread-local-variable-initval dvar) val))
      (setf (thread-local-variable-initfun dvar)
            (mop:slot-definition-initfunction key)))
    dvar))

The rest of the implementation of DYNAMIC-EFFECTIVE-SLOT is unchanged:

(defmethod mop:slot-value-using-class
    ((class standard-class)
     object
     (slotd dynamic-effective-slot))
  (dref (slot-dvar object slotd)))

(defmethod (setf mop:slot-value-using-class)
    (new-value
     (class standard-class)
     object
     (slotd dynamic-effective-slot))
  (dset (slot-dvar object slotd) new-value))

(defmethod mop:slot-boundp-using-class
  ((class standard-class)
   object
   (slotd dynamic-effective-slot))
  (dynamic-variable-bound-p (slot-dvar object slotd)))

(defmethod mop:slot-makunbound-using-class
  ((class standard-class)
   object
   (slotd dynamic-effective-slot))
  (dynamic-variable-makunbound (slot-dvar object slotd)))

The implementation of CLASS-WITH-DYNAMIC-SLOTS is also very similar. The first difference in that ALLOCATE-INSTANCE calls MAKE-DYNAMIC-VARIABLE-USING-KEY instead of MAKE-DYNAMIC-VARIABLE and supplies the effective slot definition as the key, and the instance initargs as the remaining arguments. Note that at this point initargs are already validated by MAKE-INSTANCE. The second difference is that MOP:COMPUTE-EFFECTIVE-SLOT-DEFINITION binds the flag *KLUDGE/MOP-DEFICIENCY/DYNAMIC-VARIABLE-TYPE* to DYNAMIC-VARIABLE-TYPE.

;;; This is a metaclass that allows defining dynamic slots that are bound with
;;; the operator SLOT-DLET, and, depending on the type, may have thread-local
;;; top value.
;;;
;;; The metaclass CLASS-WITH-DYNAMIC-SLOTS specifies alternative effective slot
;;; definitions for slots with an initarg :dynamic.
(defclass class-with-dynamic-slots (standard-class) ())

;;; Class with dynamic slots may be subclasses of the standard class.
(defmethod mop:validate-superclass ((class class-with-dynamic-slots)
                                    (super standard-class))
  t)

;;; When allocating the instance we initialize all slots to a fresh symbol that
;;; represents the dynamic variable.
(defmethod allocate-instance ((class class-with-dynamic-slots) &rest initargs)
  (let ((object (call-next-method)))
    (loop for slotd in (mop:class-slots class)
          when (typep slotd 'dynamic-effective-slot) do
            (setf (mop:standard-instance-access
                   object
                   (mop:slot-definition-location slotd))
                  (apply #'make-dynamic-variable-using-key slotd initargs)))
    object))

;;; To improve potential composability of CLASS-WITH-DYNAMIC-SLOTS with other
;;; metaclasses we treat specially only slots that has :DYNAMIC in initargs,
;;; otherwise we call the next method.
(defmethod mop:direct-slot-definition-class
    ((class class-with-dynamic-slots) &rest initargs)
  (loop for (key) on initargs by #'cddr
        when (eq key :dynamic)
          do (return-from mop:direct-slot-definition-class
               (find-class 'dynamic-direct-slot)))
  (call-next-method))

(defmethod mop:compute-effective-slot-definition
    ((class class-with-dynamic-slots)
     name
     direct-slotds)
  (declare (ignore name))
  (let ((latest-slotd (first direct-slotds)))
    (if (typep latest-slotd 'dynamic-direct-slot)
        (let ((*kludge/mop-deficiency/dynamic-variable-type*
                (dynamic-variable-type latest-slotd)))
          (call-next-method))
        (call-next-method))))

(defmethod mop:effective-slot-definition-class
    ((class class-with-dynamic-slots) &rest initargs)
  (declare (ignore initargs))
  (if *kludge/mop-deficiency/dynamic-variable-type*
      (find-class 'dynamic-effective-slot)
      (call-next-method)))

Finally the implementation of SLOT-DLET does not change:

;;; Accessing and binding symbols behind the slot. We don't use SLOT-VALUE,
;;; because it will return the _value_ of the dynamic variable, and not the
;;; variable itself.
(defun slot-dvar (object slotd)
  (check-type slotd dynamic-effective-slot)
  (mop:standard-instance-access
   object (mop:slot-definition-location slotd)))

(defun slot-dvar* (object slot-name)
  (let* ((class (class-of object))
         (slotd (find slot-name (mop:class-slots class)
                      :key #'mop:slot-definition-name)))
    (slot-dvar object slotd)))

(defmacro slot-dlet (bindings &body body)
  `(dlet ,(loop for ((object slot-name) val) in bindings
                collect `((slot-dvar* ,object ,slot-name) ,val))
     ,@body))

Finally we can define a class with slots that do not share the top value:

DYNAMIC-VARS> (defclass c1 ()
                  ((slot1 :initarg :slot1 :dynamic nil :accessor slot1)
                   (slot2 :initarg :slot2 :dynamic t   :accessor slot2)
                   (slot3 :initarg :slot3 :dynamic :tls :accessor slot3))
                  (:metaclass class-with-dynamic-slots))
#<The EU.TURTLEWARE.DYNAMIC-VARS::CLASS-WITH-DYNAMIC-SLOTS EU.TURTLEWARE.DYNAMIC-VARS::C1>
DYNAMIC-VARS> (with-slots (slot1 slot2 slot3) *object*
                (setf slot1 :x slot2 :y slot3 :z)
                (list slot1 slot2 slot3))
(:X :Y :Z)
DYNAMIC-VARS> (bt:make-thread
               (lambda ()
                 (with-slots (slot1 slot2 slot3) *object*
                   (setf slot1 :i slot2 :j slot3 :k)
                   (print (list slot1 slot2 slot3)))))

#<process "Anonymous thread" 0x7f76424c0240>

(:I :J :K) 
DYNAMIC-VARS> (with-slots (slot1 slot2 slot3) *object*
                (list slot1 slot2 slot3))
(:I :J :Z)

What can we use it for?

Now that we know how to define thread-local variables, we are left with a question what can we use it for. Consider having a line-buffering stream. One possible implementation could be sketched as:

(defclass line-buffering-stream (fancy-stream)
  ((current-line :initform (make-adjustable-string)
                 :accessor current-line)
   (current-ink :initform +black+
                :accessor current-ink)))

(defmethod stream-write-char ((stream line-buffering-stream) char)
  (if (char= char #
ewline)
      (terpri stream)
      (vector-push-extend char (current-line stream))))

(defmethod stream-terpri ((stream line-buffering-stream))
  (%put-line-on-screen (current-line stream) (current-ink stream))
  (setf (fill-pointer (current-line stream)) 0))

If this stream is shared between multiple threads, then even if individual operations and %PUT-LINE-ON-SCREEN are thread-safe , we have a problem. For example FORMAT writes are not usually atomic and individual lines are easily corrupted. If we use custom colors, these are also a subject of race conditions. The solution is as easy as making both slots thread-local. In that case the buffered line is private to each thread and it is put on the screen atomically:

(defclass line-buffering-stream (fancy-stream)
  ((current-line
    :initform (make-adjustable-string)
    :accessor current-line
    :dynamic :tls)
   (current-ink
    :initform +black+
    :accessor current-ink
    :dynamic :tls))
  (:metaclass class-with-dynamic-slots))

Technique is not limited to streams. It may benefit thread-safe drawing, request processing, resource management and more. By subclassing DYNAMIC-VARIABLE we could create also variables that are local to different objects than processes.

I hope that you've enjoyed reading this post as much as I had writing it. If you are interested in a full standalone implementation, with tests and system definitions, you may get it here. Cheers!




re

ALL SOULS NIGHT Complete! In One Easy-to-Read Location!

.


This year's Halloween story, written on leaves and serialized daily on my blog, one sentence at a time, is done. Every day in October, I added to it, it reached its last words on Halloween.

Funny thing, though. In conversations with two different friends, I learned that neither of them had realized it was a story. They each thought I was just posting random sentences written on leaves. One of them is an artist, and thinks primarily in visual terms, so I thought at first that was a misunderstanding curious to her. The other, however, is a well-known writer and, what's more important, quite a good one. I have no idea what's going on there.

Long story short, at my behest, my son Sean, put all the photos up on Imgur, subtitled. So, if you didn't realize that they told a story... Or if, somehow, you weren't able to hold all the sentences in your head until the story was complete... Now you can find out what was going on. (The stone angels mark the ends of paragraphs.)

You can find it by clicking on the link here.


*