Awwwwwwwwhhhhhhhh!
(Don't read the comments; they're frighteningly mad, even by the standards of large blogs.)
Wednesday, October 31, 2007
Text Messaging UI
Everyone loves text messages, right? GSM's killer app, and all that.
Well, maybe not. I can't stand them, or at least I can't stand writing them. And it's all my phone's fault. Now, I like my phone, generally. It's about two years old and hasn't broken yet, and the battery still behaves sensibly. It does, however, have two issues.
First, in common with most small consumer electronics these days, the keys are apparently made for people with miniature hands. Okay, not much to be done about that, though I can see it really becoming a problem for hardware manufacturers; the things just keep getting smaller and smaller, and peoples' fingers stubbornly refuse to follow suit. This phone doesn't even have terribly small keys, as phones go; I've seen many with far smaller. It just doesn't make sense. I don't, by the way, have unreasonably large hands or anything, either.
The other, you would think fixable, issue is that it's really slow. It takes a while to write a text message, largely because I have to wait until the letters appear on the screen to check it has the right word. For a fairly short word, this can take a second or more, and is infuriating. I'm sure a lot of this is spent rendering the rather pretty anti-aliased font; very nice, but do we really need anti-aliased text on phones?
The weird bit is, my first mobile phone didn't have either of these issues. It had a miniature monochrome screen, but the buttons were adequate, and it was nice and fast. Its battery died some time ago.
Is this progress? Really? In ten years, will we have to use toothpicks to press the tiny buttons, and wait five minutes for textured 3d letters to glide into view?
Also, the phonebook is very hard to use, but I suspect that that is because Motorola's UI team are not the best; it's not a speed issue.
Well, maybe not. I can't stand them, or at least I can't stand writing them. And it's all my phone's fault. Now, I like my phone, generally. It's about two years old and hasn't broken yet, and the battery still behaves sensibly. It does, however, have two issues.
First, in common with most small consumer electronics these days, the keys are apparently made for people with miniature hands. Okay, not much to be done about that, though I can see it really becoming a problem for hardware manufacturers; the things just keep getting smaller and smaller, and peoples' fingers stubbornly refuse to follow suit. This phone doesn't even have terribly small keys, as phones go; I've seen many with far smaller. It just doesn't make sense. I don't, by the way, have unreasonably large hands or anything, either.
The other, you would think fixable, issue is that it's really slow. It takes a while to write a text message, largely because I have to wait until the letters appear on the screen to check it has the right word. For a fairly short word, this can take a second or more, and is infuriating. I'm sure a lot of this is spent rendering the rather pretty anti-aliased font; very nice, but do we really need anti-aliased text on phones?
The weird bit is, my first mobile phone didn't have either of these issues. It had a miniature monochrome screen, but the buttons were adequate, and it was nice and fast. Its battery died some time ago.
Is this progress? Really? In ten years, will we have to use toothpicks to press the tiny buttons, and wait five minutes for textured 3d letters to glide into view?
Also, the phonebook is very hard to use, but I suspect that that is because Motorola's UI team are not the best; it's not a speed issue.
Labels:
phones,
stupid,
Technology,
ui
Tuesday, October 30, 2007
Charming company Ace Internet Marketing picks the wrong blog to rip off
Sadly, it is, on the Internet, pretty common for bottom-feeders to try to pass real peoples' work off as their own. And so it was when Ace Internet Marketing, without asking, published an article which Damien Mulley had just written. (Article since pulled, on Ace's side.) They do link back to him, but, I think, in such a way as to give the impression that he had authorised said pilfering.
They look the part, too; their site is liberally sprinkled with clip-art of annoying people, and so on. Never deal with a company which has stock photography of insanely grinning women on it. The site is written by people who don't understand the concept of apostrophes; again, generally not a great sign. Want to hire them to design your website?
On that note, by the way, they list three client case studies. Study one says it was designed by study two, and study three says it was hosted by study two. And they share an office with study two. I'm confused now.
They did email to apologise (while trying to imply that it was okay, because he'd been credited; it's a little-known fact that you're allowed steal things as long as you mention who you stole them from; it's where I get all my gadgets), and pulled the piece. Apparently, normally their contractor writes their own stuff; interesting, as their blog is rather other-people's-content-heavy. Of course, they may have asked permission from all the rest of them. They then told Mulley that he was being defamatory. Right, that's sane. And classic crazy Internet person behaviour, of course.
Ironically, one of the services that they offer is "Online Copywriting", where they say they'll sell you articles. Classy.
(Update: Have I mentioned that they're CLASSY? Classy.)
They look the part, too; their site is liberally sprinkled with clip-art of annoying people, and so on. Never deal with a company which has stock photography of insanely grinning women on it. The site is written by people who don't understand the concept of apostrophes; again, generally not a great sign. Want to hire them to design your website?
On that note, by the way, they list three client case studies. Study one says it was designed by study two, and study three says it was hosted by study two. And they share an office with study two. I'm confused now.
They did email to apologise (while trying to imply that it was okay, because he'd been credited; it's a little-known fact that you're allowed steal things as long as you mention who you stole them from; it's where I get all my gadgets), and pulled the piece. Apparently, normally their contractor writes their own stuff; interesting, as their blog is rather other-people's-content-heavy. Of course, they may have asked permission from all the rest of them. They then told Mulley that he was being defamatory. Right, that's sane. And classic crazy Internet person behaviour, of course.
Ironically, one of the services that they offer is "Online Copywriting", where they say they'll sell you articles. Classy.
(Update: Have I mentioned that they're CLASSY? Classy.)
Losing Glasses
I was at a party last night, and, as you do, I took off my glasses; I think that I look marginally less horrifying without them. Anyway, I put them in my pocket, and they must have fallen out.
Fortunately, I got them back today. The interesting thing is, when you haven't had glasses on for a while, and you put them back on, you can barely manage all the extra information. It's really quite strange. Your head spins.
Of course, last time I did this, it was at a lesbian night a year or so ago; one of the lenses in my old glasses vanished, and, naturally, I joked that it had been eaten by lesbians. Well, it happens. I assume.
Fortunately, I got them back today. The interesting thing is, when you haven't had glasses on for a while, and you put them back on, you can barely manage all the extra information. It's really quite strange. Your head spins.
Of course, last time I did this, it was at a lesbian night a year or so ago; one of the lenses in my old glasses vanished, and, naturally, I joked that it had been eaten by lesbians. Well, it happens. I assume.
Monday, October 29, 2007
More on Three
As I mentioned, my Internet service was blocking most non-HTTP services, including ssh, earlier today. They abruptly came back on at about four. Bizarre, and quite annoying.
Bloody Three
I've heard a lot about how awful Three's 3G broadband service is supposed to be. I've also had it for a couple of months, and have had no real complaints. Until now.
You see, I'd never generally be home during the day on weekdays. I'm generally in work until at least seven, and by the time I get home all is well. On weekends, the service seems to work correctly too, throughout the day.
Today's a bank holiday Monday, though, so I'm at home. Well, it turns out that during the day on weekdays, even bank holidays, they appear to block all services except HTTP. MSN won't work, nor will ssh, nor Cisco VPN.
I can see that, obviously, they have to block things like bittorrent during peak times (though are weekdays during the day peak times? Why?), but surely ssh couldn't do that much damage?
Anyway, it's a bit mad, and I'm beginning to see why other people have issues with it...
You see, I'd never generally be home during the day on weekdays. I'm generally in work until at least seven, and by the time I get home all is well. On weekends, the service seems to work correctly too, throughout the day.
Today's a bank holiday Monday, though, so I'm at home. Well, it turns out that during the day on weekdays, even bank holidays, they appear to block all services except HTTP. MSN won't work, nor will ssh, nor Cisco VPN.
I can see that, obviously, they have to block things like bittorrent during peak times (though are weekdays during the day peak times? Why?), but surely ssh couldn't do that much damage?
Anyway, it's a bit mad, and I'm beginning to see why other people have issues with it...
Labels:
3g,
broadband,
Technology,
three
Sunday, October 28, 2007
Programmer smugness
I have to admit, I experience a little moment of smugness every time I visit a Movable Type blog which is using my little email comment subscription plugin. Yes, the one which is in serious need of updating; I really must do that...
Labels:
"movable type",
Personal,
projects,
smug,
Technology
So, who was the first American in space?
Everyone knows that the first person in space was Soviet cosmonaut Yuri Gagarin. There is little room for dispute there; he orbited the Earth, and landed.
Ah, but, who was the first American in space? Well, there, there's a little bit of ambiguity. Shortly after the launch of Vostok I, Alan Shepard made a suborbital space flight, launched by a Redstone rocket (a variety of medium-range ballistic missile). This was similar in nature to the recent flights of Scaled Composites' SpaceShip One; he made a flight into space, but did not enter orbit; launching a spaceship into orbit is a considerably greater feat.
The first American to orbit the Earth, and the one you've probably heard of, was, of course, John Glenn, in a Mercury capsule launched on an Atlas ICBM, a little later.
Of course, the reason that you don't here much about Shepard is because his flight was, well, rather pathetic. It was made after the Soviet Union had already made a successful orbital flight. It's almost surprising that it was made at all; from a propaganda point of view (and propaganda was what early manned space flight was all about) it would probably have made more sense to go ahead with a proper orbital launch as soon as possible.
Regardless, the whole thing causes enormous confusion on Wikipedia talk pages and suchlike. :)
Ah, but, who was the first American in space? Well, there, there's a little bit of ambiguity. Shortly after the launch of Vostok I, Alan Shepard made a suborbital space flight, launched by a Redstone rocket (a variety of medium-range ballistic missile). This was similar in nature to the recent flights of Scaled Composites' SpaceShip One; he made a flight into space, but did not enter orbit; launching a spaceship into orbit is a considerably greater feat.
The first American to orbit the Earth, and the one you've probably heard of, was, of course, John Glenn, in a Mercury capsule launched on an Atlas ICBM, a little later.
Of course, the reason that you don't here much about Shepard is because his flight was, well, rather pathetic. It was made after the Soviet Union had already made a successful orbital flight. It's almost surprising that it was made at all; from a propaganda point of view (and propaganda was what early manned space flight was all about) it would probably have made more sense to go ahead with a proper orbital launch as soon as possible.
Regardless, the whole thing causes enormous confusion on Wikipedia talk pages and suchlike. :)
Labels:
"cold war",
"soviet union",
america,
space,
Technology
On the USI Controversy
A while ago, there was a major hurricane in the rather small teacup that is Irish student politics. A memo by Stephen Conlon, then the USI equality officer, calling for resignation of the president, was leaked, ultimately leading to a vote of no confidence against the president. The USI, by the way, is the decrepit and crumbling old national students' union.
Now, before I go on, I should mention that I am not Conlon's greatest fan. In fact (try to imagine me as Lucille from Arrested Development here), "I don't care for Conlon." I was, you see, on the USI LGBT working group during the same period that he was there. Now, I had no particular reason to dislike him, I think it was just a conflict of personalities. He was full of himself to a degree which I find annoying. I also dislike being patronised by not-very-clever people.
The thing is, at a certain point he took over the running of the LGBT working group, first, rather unofficially, later as an elected rights officer. Shortly thereafter, he sent out an email saying that all members of the group should take a united stand on the group's positions, and not speak out against them. This was one of a number of reasons that I gave up on USI, by the way; I don't think that it's a sensible position. I've just remembered all this; in the context of the memo, it is incredibly ironic.
Here we go:
USI LGBT Working Group, then, is a unified campaign, but USI itself not so much, it would seem.
By the way:
Heheh.
Good lord, I'm glad I abandoned the sinking ship when I did...
Now, before I go on, I should mention that I am not Conlon's greatest fan. In fact (try to imagine me as Lucille from Arrested Development here), "I don't care for Conlon." I was, you see, on the USI LGBT working group during the same period that he was there. Now, I had no particular reason to dislike him, I think it was just a conflict of personalities. He was full of himself to a degree which I find annoying. I also dislike being patronised by not-very-clever people.
The thing is, at a certain point he took over the running of the LGBT working group, first, rather unofficially, later as an elected rights officer. Shortly thereafter, he sent out an email saying that all members of the group should take a united stand on the group's positions, and not speak out against them. This was one of a number of reasons that I gave up on USI, by the way; I don't think that it's a sensible position. I've just remembered all this; in the context of the memo, it is incredibly ironic.
Here we go:
The fact is if someone doesn't reply and a press release then goes out 'bitching' about it....it will not be tolerated. And furthermore if a press release is sent out by the campaign/working group....it is sent out by the campaign/working group. Ergo....no posting on forums saying 'I'm the lone voice on working group against this'. Simply put this will under no circumstances be tolerated. We are a unified campaign, mandated to get work done, not a talk shop. If its policy, if its agreed by the majority that reply then sin é! We must be seen to be unified at all times....the campaign is starting to once more build up a reputation and I hope by the end of my term it will be seen as an 'expert group' that can be approached by not alone the LGBT media, but mainstream and government as well.
USI LGBT Working Group, then, is a unified campaign, but USI itself not so much, it would seem.
By the way:
The memo called for the resignation of Morrisroe on a number of grounds, the most serious of which was ruling “according to his own whims.”
Heheh.
Good lord, I'm glad I abandoned the sinking ship when I did...
Labels:
"student politics",
"unpleasant people",
college,
funny,
politics,
usi
What price a Facebook app?
Apparently, the owners of a very popular Facebook application (4 million users) only get a quarter of a million visits to their website a month. (According to Compete.com; US-centric numbers, but they give a reasonable idea.)
This is probably not terribly surprising, and shows up a major weakness in the Facebook platform (from a developer's point of view); there is no particularly obvious way to make money from a successful application.
There have now been a few Facebook applications sold, mostly on eBay. Prices are generally unimpressive, and certainly don't compare well to prices achieved for websites with similar traffic.
This is something that Facebook really needs to deal with if they want people to continue writing for their platform. It's all very well to have a Facebook application with millions of users, but if you can't make money out of it, it starts to look like a bit of a liability; hosting is not free, and you could probably make more money out of a real website.
This is probably not terribly surprising, and shows up a major weakness in the Facebook platform (from a developer's point of view); there is no particularly obvious way to make money from a successful application.
There have now been a few Facebook applications sold, mostly on eBay. Prices are generally unimpressive, and certainly don't compare well to prices achieved for websites with similar traffic.
This is something that Facebook really needs to deal with if they want people to continue writing for their platform. It's all very well to have a Facebook application with millions of users, but if you can't make money out of it, it starts to look like a bit of a liability; hosting is not free, and you could probably make more money out of a real website.
Labels:
"internet business",
facebook,
internet
Poor, poor Matt
Apparently, Wordpress isn't the only thing which inspires people to be unkind to Matt Mullenweg, and he's just as defensive about personal issues as he is about the blogging platform of doom.
In fairness, it's a bit of a cheap shot, but Valleywag is a bit of a tabloid, which is, of course, why people read it. :)
In fairness, it's a bit of a cheap shot, but Valleywag is a bit of a tabloid, which is, of course, why people read it. :)
Labels:
autocrattic,
random,
scandal,
valleywag,
wordpress
Saturday, October 27, 2007
Triumph of the Mobile Phone?
Have you noticed that many Americans now say "mobile phone" instead of "cell phone"? Especially on technical blogs, it seems to be gaining popularity. Cell phone was always a bit of an oddity; it describes more how the device works than what it actually does.
I was hoping that Google Trends would support my theory that Americans are dropping the "cell phone", but, as it often is, it is irritatingly inconclusive.

I was hoping that Google Trends would support my theory that Americans are dropping the "cell phone", but, as it often is, it is irritatingly inconclusive.

Labels:
"cell phone",
"mobile phone",
random,
words
Zoho isn't Google, goodness me, no
A new, very Web 2.0, startup called Zoho is trying to do AJAX office apps. Now, reportedly, they're pretty awful. You have to sign up to use them, and it doesn't seem to work in Safari (it fails rather ungracefully, with a big brown screen), so I haven't bothered actually checking.
However, I can confirm that their login screens look frighteningly like Google's, though they do have odder URLs. Perhaps they aim to make any acquisition particularly easy? They'd basically just have to change the logos. For a proper company, it is amazingly rip-off-tastic.
However, I can confirm that their login screens look frighteningly like Google's, though they do have odder URLs. Perhaps they aim to make any acquisition particularly easy? They'd basically just have to change the logos. For a proper company, it is amazingly rip-off-tastic.
Labels:
"web 2.0",
google,
Technology
Want to be a programmer?
I was in a random pub on George's Street last night. As one does, at some point, I visited the toilet. There, on the back of a cubicle door, was a recruitment ad for programmers. Complete with stupid little snippet of Java code (a class with all static methods, rather defeating the purpose of using a class, you might think) which printed out the name of the company's website.
Lovely. I assume this is 'bottom of the barrel' programmer recruitment; the ones who are impressed by silly Java ads in toilet cubicles.
Lovely. I assume this is 'bottom of the barrel' programmer recruitment; the ones who are impressed by silly Java ads in toilet cubicles.
Labels:
advertising,
Programming,
stupid,
toilet
An Apology
If I've been particularly unpleasant to you in the last few days, it is likely because I'd barely eaten since lunch time on Wednesday. Sorry about that. Foolish, but I didn't really get around to it. Anyway, all better now.
Of course, if I was particularly unpleasant to you before Wednesday, well, that's just my way, and is to be expected. The limited apology does not cover it.
On the plus side, I'm not feeling as fat as normal.
Of course, if I was particularly unpleasant to you before Wednesday, well, that's just my way, and is to be expected. The limited apology does not cover it.
On the plus side, I'm not feeling as fat as normal.
Friday, October 26, 2007
Breakfast at Microsoft
Apparently, this is actual promotional material from one of Microsoft's cafeterias:
Argh.
Being a small company, we don't have a cafeteria, of course, but then we have all of Dublin to choose from. Probably a bit more expensive that MS's setup, but the sales pitches aren't quite so nauseating.
I mean, really. Microsoft employs probably some of the smartest programmers in the world, and then advertises at them like they're watching a shopping channel at 3 in the morning. Ludicrous.
As an aside, the blog I found this on is that of a Microsoft employee, on TypePad. So even some Microsoft employees can't bring themselves to use that frightening (and very slow) ASP.NET blogging platform that MSDN favours, then...
"You'll love
the flash in the flame-fired action as well as in the speed-of-service
as our chefs bring razzle-dazzle and plenty of sizzle to each
made-to-order entree".
Argh.
Being a small company, we don't have a cafeteria, of course, but then we have all of Dublin to choose from. Probably a bit more expensive that MS's setup, but the sales pitches aren't quite so nauseating.
I mean, really. Microsoft employs probably some of the smartest programmers in the world, and then advertises at them like they're watching a shopping channel at 3 in the morning. Ludicrous.
As an aside, the blog I found this on is that of a Microsoft employee, on TypePad. So even some Microsoft employees can't bring themselves to use that frightening (and very slow) ASP.NET blogging platform that MSDN favours, then...
Stress is great, sometimes
It is amazingly effective at distracting me from the disaster that is my personal life.
The problem, of course, is that stress eventually has a way of reducing, as it is now. I'm ending up thinking about me again, which is rarely a good idea, when it comes to it.
Bah.
The problem, of course, is that stress eventually has a way of reducing, as it is now. I'm ending up thinking about me again, which is rarely a good idea, when it comes to it.
Bah.
Thursday, October 25, 2007
More Google Quirks
Google now lets you add search results to their notebook:
With this result:

Duly noted, indeed!
With this result:
Duly noted, indeed!
Labels:
funny,
google,
notebook,
Technology
Search technology at its finest
Yahoo has just added a lovely little drop-down box to their search site; when you search for something, it gives you 'related concepts'.
The concepts supplied for me are interesting. Highlights:
The concepts supplied for me are interesting. Highlights:
- Gender - Do I have more of it than other people, or something?
- Tesco - Damn! I wanted to be Aldi!
- Lifeline - Do they think I need one?
- Demonware - Uncanny of them. I suspect they're cribbing off Linked-In or Facebook.
- Hate the Summer - Why, yes, I do!
- Search ratings - I am NOT an evil SEO person, thanks so much.
Labels:
ai,
search,
Technology,
yahoo
Wednesday, October 24, 2007
Google Reader grows up!
Google Reader no longer has either a 'beta' or 'labs' thing at the top! Well done; that only took three years or so.
GMail still has 'beta' at the top, though, and probably always will.
GMail still has 'beta' at the top, though, and probably always will.
Labels:
gmail,
google,
reader,
Technology
Tuesday, October 23, 2007
Thanks, Google. Thoogle.
As I've previously mentioned, my poor little website's visit count was devastated by its domain expiring for a few days; Google practically delisted it.
Well, anyway, it seems to be on the mend; visitor numbers are heading up and it's bobbing back up the search ratings. Yay! That's a relief...
Well, anyway, it seems to be on the mend; visitor numbers are heading up and it's bobbing back up the search ratings. Yay! That's a relief...
Labels:
findmeatune,
google,
Technology
Cosmetic Issues
Is it ridiculous that I prefer writing stuff for this blog now that it has a prettier theme?
Well, yes, probably.
Still, that's life, isn't it?
Well, yes, probably.
Still, that's life, isn't it?
Monday, October 22, 2007
Quote of the day
From a random blog:
I have not the words.
Your wife might get some ideas from www.homestead.org in the forum section. Great for money saving tips and tricks, also for learning how to slaughter your goats!
I have not the words.
Labels:
goats,
organic,
quotes,
ridiculous,
stupid
Censorship by Killer Homoeopaths
The Society of Homoeopaths attempt to censor someone for speaking out about their inability to control their members, and about the promotion of 'homoeopathic methods' (or 'insane, destructive lies') as a suitable treatment for Malaria in Kenya. (Homoeopathy is unscientific voodoo nonsense, as you will no doubt recall, involving giving people water.)
Sometimes I love the Internet; the cease and desist letter has lost a lot of its power. Of course, a frightening number of people are siding with the homoeopaths.
While I'm all for free speech generally, this sort of thing (and especially the promotion of homoeopathy to deal with serious diseases which can be better dealt with through actual medicine) is a bit much. A hypothetical question, here; if somebody gives flawed 'medical' advice to credulous people in the Developing World (or the Developed World, for that matter) which leads to the painful deaths of tens of thousands of people, and which they know themselves to be nonsense, how to they rank morally compared to, say, a minor genocide?
Sometimes I love the Internet; the cease and desist letter has lost a lot of its power. Of course, a frightening number of people are siding with the homoeopaths.
While I'm all for free speech generally, this sort of thing (and especially the promotion of homoeopathy to deal with serious diseases which can be better dealt with through actual medicine) is a bit much. A hypothetical question, here; if somebody gives flawed 'medical' advice to credulous people in the Developing World (or the Developed World, for that matter) which leads to the painful deaths of tens of thousands of people, and which they know themselves to be nonsense, how to they rank morally compared to, say, a minor genocide?
Labels:
censorship,
immoral,
lies,
medicine,
Reputable Businessmen
The hangover cure that wasn't
As you will no doubt remember, last year Dublin was covered in publicity for a non-drug which implied that if you took it before drinking, you wouldn't have a hangover. Quackery, of course.
I just saw a packet today. Fascinating. It alludes to the whole 'this might cure hangovers' thing; the small-print, however, explicitly says that it does not help with the effects of heavy drinking. And I quote:
Big print:
"Partying tonight? Busy tomorrow? Get a Lifeline. As seen on TV. A vitamin and mineral food supplement with activated charcoal. For people who like a party lifestyle... You'll still be able to think straight if you've taken your Lifeline."
Small print:
"To prevent dehydration from alcohol drink plenty of water or sports drinks... This product cannot, nor is it intended to lessen or prevent intoxication from excessive consumption of alcohol in any way. It is not intended to diagnose, treat or prevent any disease or the consequences of excessive alcohol consumption..." [So, basically, either the copywriter is schizophrenic, or they are just covering their ass and hoping that no-one reads the small-print...]
From the website:
"Lifeline was developed in Ireland, where we really know how to party - so you know it works!"
Shill-tastic.
It also, rather amazingly, has 2462% the recommended daily allowance of Vitamin B-12.
They have one unclear study, which they claim to be scientific proof that their product is effective. No pharmaceutical license, by the way!
I'm honestly quite amazed that any of this is legal. The "you'll still be able to think straight", in particular, seems fairly medical-claim-ish. There was an upheld ASA complaint about them, but it seems to be more on the basis that they were glamorising alcohol consumption than anything else...
The pills in question were given out by a Trinity College society. Could be worse; if it had been the Students' Union I'd be really annoyed.
Anyway, since I am busy tomorrow, though not partying tonight, I had better get some sleep...
I just saw a packet today. Fascinating. It alludes to the whole 'this might cure hangovers' thing; the small-print, however, explicitly says that it does not help with the effects of heavy drinking. And I quote:
Big print:
"Partying tonight? Busy tomorrow? Get a Lifeline. As seen on TV. A vitamin and mineral food supplement with activated charcoal. For people who like a party lifestyle... You'll still be able to think straight if you've taken your Lifeline."
Small print:
"To prevent dehydration from alcohol drink plenty of water or sports drinks... This product cannot, nor is it intended to lessen or prevent intoxication from excessive consumption of alcohol in any way. It is not intended to diagnose, treat or prevent any disease or the consequences of excessive alcohol consumption..." [So, basically, either the copywriter is schizophrenic, or they are just covering their ass and hoping that no-one reads the small-print...]
From the website:
"Lifeline was developed in Ireland, where we really know how to party - so you know it works!"
Shill-tastic.
It also, rather amazingly, has 2462% the recommended daily allowance of Vitamin B-12.
They have one unclear study, which they claim to be scientific proof that their product is effective. No pharmaceutical license, by the way!
I'm honestly quite amazed that any of this is legal. The "you'll still be able to think straight", in particular, seems fairly medical-claim-ish. There was an upheld ASA complaint about them, but it seems to be more on the basis that they were glamorising alcohol consumption than anything else...
The pills in question were given out by a Trinity College society. Could be worse; if it had been the Students' Union I'd be really annoyed.
Anyway, since I am busy tomorrow, though not partying tonight, I had better get some sleep...
Labels:
alcohol,
hangover,
health,
quacks,
Reputable Businessmen
Quiver Quiver Wobble Wobble
I really, really, need to start going to the swimming pool. Also eating less; bah.
Of course, the fatter I become, the less inclined I am to go swimming; it does, when it comes to it, involve removing clothes...
I may be being over-paranoid; I probably actually weigh less than I did a couple of months ago. Definitely in worse shape, though.
Of course, the fatter I become, the less inclined I am to go swimming; it does, when it comes to it, involve removing clothes...
I may be being over-paranoid; I probably actually weigh less than I did a couple of months ago. Definitely in worse shape, though.
Sunday, October 21, 2007
New blog theme!
Isn't it pretty?
Or, well, at least prettier than the last one; wouldn't be hard. It's another Six Apart-supplied one, incidentally; aftermarket themes for MT4 still seem to be thin on the ground as yet.
Still far too wide, though; bah. Do they think we all have dual 30" Apple Cinema displays, or something? Well, do they? (I have dual 20" monitors at work, but an elderly 12" Apple laptop at home; the whole screen width thing gets annoying.)
As an aside, Queer as Folk (or at least the US version) is absolutely revolting. Ewh.
Or, well, at least prettier than the last one; wouldn't be hard. It's another Six Apart-supplied one, incidentally; aftermarket themes for MT4 still seem to be thin on the ground as yet.
Still far too wide, though; bah. Do they think we all have dual 30" Apple Cinema displays, or something? Well, do they? (I have dual 20" monitors at work, but an elderly 12" Apple laptop at home; the whole screen width thing gets annoying.)
As an aside, Queer as Folk (or at least the US version) is absolutely revolting. Ewh.
Labels:
"movable type",
blogging,
media,
themes
Getting ID of last inserted record using CLSQL and MySQL
CLSQL is a free database interface library for Common Lisp. It supports multiple databases, including MySQL.
MySQL's C API has a very handy feature, exposed by most MySQL access libraries, which gives you the ID of the last record inserted on your connection. It is, of course, dependent on your table having an auto-incrementing ID field.
It's not obvious from the documentation (in fact, none of CLSQL's backend-specific functionality is terribly well-documented), but you can get this data in CLSQL, too. Just do this:
(clsql-mysql::mysql-insert-id (clsql-mysql::database-mysql-ptr clsql:*default-database*))
Of course, if you're using an explicit DB connection handle, substitute that. There are a couple of other handy functions exposed which you can use the same technique to get at, too.
MySQL's C API has a very handy feature, exposed by most MySQL access libraries, which gives you the ID of the last record inserted on your connection. It is, of course, dependent on your table having an auto-incrementing ID field.
It's not obvious from the documentation (in fact, none of CLSQL's backend-specific functionality is terribly well-documented), but you can get this data in CLSQL, too. Just do this:
(clsql-mysql::mysql-insert-id (clsql-mysql::database-mysql-ptr clsql:*default-database*))
Of course, if you're using an explicit DB connection handle, substitute that. There are a couple of other handy functions exposed which you can use the same technique to get at, too.
MovableType 4.01
I just upgraded this blog to MovableType 4.01 (from 4.0 rc4). I doubt it has made much difference; it's just performance and security fixes, but if you see any problems let me know.
More MySQL weirdness - When is a boolean not a boolean?
Here's another MySQL performance oddity I've noticed; a really weird one, this time.
Create a table with a large amount of data in it, and at least one column which can have a value of either 0 or 1. Run lots of SELECTs against it based (in whole or part) on the value of that column. Now try doing the same, but with TRUE or FALSE. You'll notice (or at least may notice; it's possible that it was chance or experimental error on my part) that the second way is slower. EXPLAIN shows nothing weird...
My only guess is that it's a query compilation thing, but I really wouldn't expect it to behave that way, even so.
Create a table with a large amount of data in it, and at least one column which can have a value of either 0 or 1. Run lots of SELECTs against it based (in whole or part) on the value of that column. Now try doing the same, but with TRUE or FALSE. You'll notice (or at least may notice; it's possible that it was chance or experimental error on my part) that the second way is slower. EXPLAIN shows nothing weird...
My only guess is that it's a query compilation thing, but I really wouldn't expect it to behave that way, even so.
J K Rowling in Gay Wizard Shocker
Apparently, Dumbledore is gay. Yes, really. Expect sales of Harry Potter in the American midwest to go through the floor.
Friday, October 19, 2007
Easy Come, Easy Go - Domain registration lapse wreaks havok on Google rankings
I previously mentioned that the domain for my FindMeATune site briefly expired. I got it back since, but the couple of days pointing to a domain parking page seems to have horribly hurt its Google rankings; for terms that it was the top result for a couple of days ago, it is now no-where to be seen.
Traffic has halved, so far. I do hope that it recovers in a sensible period of time, or, really, at all. AdSense payout per thousand impressions also remains hard-hit.
Clearly, I am being punished for my foolishness...
Traffic has halved, so far. I do hope that it recovers in a sensible period of time, or, really, at all. AdSense payout per thousand impressions also remains hard-hit.
Clearly, I am being punished for my foolishness...
Labels:
domains,
findmeatune,
google,
ranking,
Technology
Thursday, October 18, 2007
Bah
Being an ugly gay guy's no fucking fun, sometimes.
At this point, I tend to wonder what the hell am I doing with my life, really? Stress, in some ways, is great, it insulates you from the whole 'all alone and likely to stay that way' thing...
The worst of it is, it tends to cause me to be a complete bastard to my friends. At least partly out of envy.
It's at times like this when I really dislike myself.
At this point, I tend to wonder what the hell am I doing with my life, really? Stress, in some ways, is great, it insulates you from the whole 'all alone and likely to stay that way' thing...
The worst of it is, it tends to cause me to be a complete bastard to my friends. At least partly out of envy.
It's at times like this when I really dislike myself.
Wednesday, October 17, 2007
Commercial Lisp; a Clarification
A few days ago, I wrote an article called "The Problem with Commercial Lisp". Much to my surprise, it was submitted to programming.reddit.com, and ended up on the front page for a while; that the piece was vaguely controversial and quite unpopular is obvious from the reddit statistics; 65 up votes, 47 down votes, and 43 comments. No comments on the entry at all, incidentally, which seems often to be the way with Reddit. Anyway.
Just to make it clear, I am not saying that there is anything wrong with charging for development tools, or that the people who write LispWorks should turn around and make it free. I have no issue with paying for this sort of software. However, I do think that it would make sense to in some way make it so that a person buying a license for the basic version of LispWorks could compile and distribute their applications for the other two major platforms without buying two more licenses. As mentioned in the original article, a conventional cross-compiler would probably not be technically feasible, but there are other options.
I think that it's very unlikely that this would hurt their bottom line too much; it would make the platform far more appealing to hobbyist users and writers of small freeware/donationware/shareware applications and so on, but their large commercial customers would presumably go on buying the enterprise versions.
I wasn't, with the original article, really demanding or even requesting anything. I tend to forget that people read this blog, and most of my pieces along the lines of the original article are just random airings of my opinions on things. I might have been more careful writing it if I thought that 2,500 people were going to view it!
A number of people also replied saying that the free Lisps could do anything I wanted. While this is more or less true for web development and other things which other people aren't actually going to have to install, it's... less true for desktop applications aimed at non-tech-savvy people.
As I've mentioned before, I'm currently (slowly) writing such an application. Unfortunately, the absence of tools oriented towards this sort of thing in the free Lisp world means that it will be a Lisp backend (running on OpenMCL for PowerPC and SBCL for Intel Macs) using an Interface Builder-created UI controlled via a slave Python process using PyObjC. Due to PyObjC's abilities to make distributable applications, and the nature of MacOS's application
bundles, I think that I should be able to make a re-distributable version which appears as a MacOS application using a PyObjC-generated distributable bundle, a Lisp memory dump, and a shell script or something to bootstrap it. There will be no Windows version.
Now, all this is a bit involved. I'm doing it this way because I think Lisp is very suited to the application logic, certainly better than, say, Java, but really, it's over the top. Uncompressed, it's likely to consume about 40MB. Now, compare how you'd do this in LispWorks. You could make the interface using the CAPI, build applications for both platforms, and the tree-shaker would reduce the size of the end product. Of course, to do this, I'd need two licenses, one for MacOS and one for Windows. That's 2400 euro. That, quite frankly, is a little mad.
A number of people also mentioned the existence of things like LTK. Now, while it's all very well for applications you're only going to use yourself, Tk is pretty ugly, and doesn't come with Windows; you'd either have to bundle it with your app or get the user to install it beforehand. Others mentioned projects which work with GTK or XWindows, and plan to add Cocoa/Win32 support. That's all very well, but of minimal use to someone who just wants to write a GUI application now.
For that matter, there is no entirely satisfactory free Lisp for Windows. There are SBCL and clisp, but neither (on Windows; SBCL does on other platforms) do threading, and clisp has that whole weird licensing issue; I'm not really sure how it effects the developer of a non-GPL application, but I'm a bit scared of it. I've also found SBCL on Windows (a few versions back now) to be rather unreliable, especially when dealing with large amounts of memory.
ABCL is another vague possibility; it's a Common Lisp for the Java Virtual Machine. However, it has, according to the author, slow CLOS and serious performance issues on Java 1.6. And, of course, by no means everyone has Java!
I'm in no way trying to make little of all the hard work that has gone into the various free Lisp implementations and libraries. I use SBCL, OpenMCL and many free libraries myself for web development and similar. However, I find that they're not, for the most part, terribly suited to making distributable applications for normal people to use. This could, of course, change, and I'd be entirely willing to help it change (assuming, of course, that I can find problems which I'm able to fix; I'm not an amazing programmer), but for the moment the commercial lisps seem the only sane way to make these sort of applications, and the pricing structure is not ideal.
So, yes, original article was really more about LispWorks' pricing structure, specifically in relation to multiple licenses being required for multiple platform copies, even if those copies are intended only for producing executables for the relevant platform.
I really must start being more careful about what I write on the Internet...
Just to make it clear, I am not saying that there is anything wrong with charging for development tools, or that the people who write LispWorks should turn around and make it free. I have no issue with paying for this sort of software. However, I do think that it would make sense to in some way make it so that a person buying a license for the basic version of LispWorks could compile and distribute their applications for the other two major platforms without buying two more licenses. As mentioned in the original article, a conventional cross-compiler would probably not be technically feasible, but there are other options.
I think that it's very unlikely that this would hurt their bottom line too much; it would make the platform far more appealing to hobbyist users and writers of small freeware/donationware/shareware applications and so on, but their large commercial customers would presumably go on buying the enterprise versions.
I wasn't, with the original article, really demanding or even requesting anything. I tend to forget that people read this blog, and most of my pieces along the lines of the original article are just random airings of my opinions on things. I might have been more careful writing it if I thought that 2,500 people were going to view it!
A number of people also replied saying that the free Lisps could do anything I wanted. While this is more or less true for web development and other things which other people aren't actually going to have to install, it's... less true for desktop applications aimed at non-tech-savvy people.
As I've mentioned before, I'm currently (slowly) writing such an application. Unfortunately, the absence of tools oriented towards this sort of thing in the free Lisp world means that it will be a Lisp backend (running on OpenMCL for PowerPC and SBCL for Intel Macs) using an Interface Builder-created UI controlled via a slave Python process using PyObjC. Due to PyObjC's abilities to make distributable applications, and the nature of MacOS's application
bundles, I think that I should be able to make a re-distributable version which appears as a MacOS application using a PyObjC-generated distributable bundle, a Lisp memory dump, and a shell script or something to bootstrap it. There will be no Windows version.
Now, all this is a bit involved. I'm doing it this way because I think Lisp is very suited to the application logic, certainly better than, say, Java, but really, it's over the top. Uncompressed, it's likely to consume about 40MB. Now, compare how you'd do this in LispWorks. You could make the interface using the CAPI, build applications for both platforms, and the tree-shaker would reduce the size of the end product. Of course, to do this, I'd need two licenses, one for MacOS and one for Windows. That's 2400 euro. That, quite frankly, is a little mad.
A number of people also mentioned the existence of things like LTK. Now, while it's all very well for applications you're only going to use yourself, Tk is pretty ugly, and doesn't come with Windows; you'd either have to bundle it with your app or get the user to install it beforehand. Others mentioned projects which work with GTK or XWindows, and plan to add Cocoa/Win32 support. That's all very well, but of minimal use to someone who just wants to write a GUI application now.
For that matter, there is no entirely satisfactory free Lisp for Windows. There are SBCL and clisp, but neither (on Windows; SBCL does on other platforms) do threading, and clisp has that whole weird licensing issue; I'm not really sure how it effects the developer of a non-GPL application, but I'm a bit scared of it. I've also found SBCL on Windows (a few versions back now) to be rather unreliable, especially when dealing with large amounts of memory.
ABCL is another vague possibility; it's a Common Lisp for the Java Virtual Machine. However, it has, according to the author, slow CLOS and serious performance issues on Java 1.6. And, of course, by no means everyone has Java!
I'm in no way trying to make little of all the hard work that has gone into the various free Lisp implementations and libraries. I use SBCL, OpenMCL and many free libraries myself for web development and similar. However, I find that they're not, for the most part, terribly suited to making distributable applications for normal people to use. This could, of course, change, and I'd be entirely willing to help it change (assuming, of course, that I can find problems which I'm able to fix; I'm not an amazing programmer), but for the moment the commercial lisps seem the only sane way to make these sort of applications, and the pricing structure is not ideal.
So, yes, original article was really more about LispWorks' pricing structure, specifically in relation to multiple licenses being required for multiple platform copies, even if those copies are intended only for producing executables for the relevant platform.
I really must start being more careful about what I write on the Internet...
Labels:
java,
lisp,
lispworks,
Programming,
pyObjC,
python,
reddit,
Technology
Nine out of Ten Dentists Recommend...
It just occurred to me that the "Nine out of Ten Dentists Recommend Prilm", or whatever, ads that purveyors of toothpaste use are not terribly reassuring. I mean, dentists make money out of having people come to them with dodgy teeth, surely? From their point of view, better toothpastes should probably contain hydrochloric acid or similar. Also, as is well known, all dentists are evil. That's why their surgeries generally look like the headquarters of a Bond villain, with mad over-complicated chairs and so forth.
There's a nice little thought for the day, eh? Not that I'm paranoid, or anything...
There's a nice little thought for the day, eh? Not that I'm paranoid, or anything...
Labels:
"evil dentists",
advertising,
medicine,
paranoia,
random,
toothpaste
Stalker-tastic!
I recently noticed that Wired.com has a 'Howtos' section, with guides on doing various things. It's linked, for reasons I'm not clear on, from the bottom of Reddit. Said guides cover amazingly diverse topics.
One caught my eye. Turn your Flickr crush into a real romance. Yes, really. How to use Web2.0 social photography sites to get laid. It tells men which women to target, and how to advertise themselves in their profile.
Yuk. I mean, really, yuk. Who uses Flickr this way?! It reads a bit like those frightening articles about the 'ladder theory', which I've mentioned before. Internet people (and especially Internet desperate men) are scary.
(Possibly I'm just jealous; my not so great good looks don't make dating via photo site a realistic option for me, you see...)
One caught my eye. Turn your Flickr crush into a real romance. Yes, really. How to use Web2.0 social photography sites to get laid. It tells men which women to target, and how to advertise themselves in their profile.
Yuk. I mean, really, yuk. Who uses Flickr this way?! It reads a bit like those frightening articles about the 'ladder theory', which I've mentioned before. Internet people (and especially Internet desperate men) are scary.
(Possibly I'm just jealous; my not so great good looks don't make dating via photo site a realistic option for me, you see...)
Labels:
dating,
flickr,
scary,
stalkers,
Technology
Tuesday, October 16, 2007
MySQL Subselect Annoyance
Possibly this is obvious to database experts, but I am not a database expert, and it annoyed me.
It turns out that if you use a subselect query to provide data to an insert, it (at least sometimes) appears to do a full table scan. Something like this.
INSERT INTO table1 VALUES(10, (SELECT email FROM table2 WHERE id = 123456));
Each query on its own is fine, but together, they are disastrous. Bah. Irritating.
It turns out that if you use a subselect query to provide data to an insert, it (at least sometimes) appears to do a full table scan. Something like this.
INSERT INTO table1 VALUES(10, (SELECT email FROM table2 WHERE id = 123456));
Each query on its own is fine, but together, they are disastrous. Bah. Irritating.
Labels:
annoyance,
databases,
mysql,
slow,
Technology
Bizarre Movable Type Feature
I've just noticed that Movable Type has a discreet little feature for spamming people. When you've written a post, a little link saying 'Share this entry' appears. If you click, you get this:

It's one of Movable Type's weird little DHTML faux popups, one of my least favourite features of the platform.
Please note that the default is to send to "All addresses from Address Book". Address Book? Eh? All addresses? A little spammy, perhaps... I know I'd be irritated if people emailed me to let me know they had a new blog entry; if I want to know that they have a new blog entry I will have their blog in my RSS reader.
Please note that the default is to send to "All addresses from Address Book". Address Book? Eh? All addresses? A little spammy, perhaps... I know I'd be irritated if people emailed me to let me know they had a new blog entry; if I want to know that they have a new blog entry I will have their blog in my RSS reader.
Labels:
"movable type",
blogging,
spam,
Technology
Three Ireland kills ICMP?
As previously mentioned, my home Internet access is currently through a 3g system provided by Three Ireland. Now, as it can be a tiny bit unreliable, I tend to ping something when I start using the computer to check if it's working properly. Today, while I can access the Internet, I can't ping anything. I assume they've blocked it... I wonder why?
Annoying, as it was occasionally a useful tool for antenna positioning.
Annoying, as it was occasionally a useful tool for antenna positioning.
Labels:
broadband,
icmp,
Technology,
three
Programming for Girls
Seen on Reddit.
I can't help thinking that people who go around creating gender-specific programming languages are more part of the problem than part of the solution. They should possibly be taking a closer look at the silly social reasons that girls will tend to avoid computer science...
You see, it's a girl's programming language because it's got a ladybird, called Kara, in it. Or something. Who knows, really?
I can't help thinking that people who go around creating gender-specific programming languages are more part of the problem than part of the solution. They should possibly be taking a closer look at the silly social reasons that girls will tend to avoid computer science...
You see, it's a girl's programming language because it's got a ladybird, called Kara, in it. Or something. Who knows, really?
Labels:
Programming,
sexism,
society
Monday, October 15, 2007
Sometimes (often?) I'm a complete idiot
As I've previously mentioned, I have a moderately successful website called FindMeATune. On Sunday, I noticed that there were very few visitors. A quick visit to the site showed that the domain was now pointing to one of Enom's page reservation services. Ooh-er. It turns out that the domain had expired on Friday; GMail had helpfully decided that the 're-register now!' notifications from Hosting365 were spam. In fairness, I can sort of understand why; they're automated, they have lots of links in them, and they use the word of doom, 'important'. It was also the only Hosting365 domain I have, and, to be honest, I'd forgotten it was still there.
Well, I'll admit it, I panicked. I mean, what exactly does happen when a domain expires? It's actually happened to me before, but that was before ICANN brought in their rather scary sounding 'redemption' thing.
Redemption, it turns out, is a process where the registrar, apparently, may or may not let you have your domain back on payment of a stupid fee; $160 for Enom (who Hosting365 resells for). That's right, they can if they feel like it, but they don't seem to be under any obligation to. Given the nature of the site, I'd be quite willing to pay, but it was a little scary that they might just refuse.
As it turns out, all was well. My domain was actually in some odd state between registered and redemption, and I was able to re-register through the normal process, for the normal price, with the help of nice helpful people at Hosting365. I know they get a fair bit of flack, but in this case at least they seemed to be quite reasonable.
The site should be back now, for most people. Interestingly, the Google AdSense price per thousand impressions for it is currently horrible; I assume their robots visited while it was a holding page and downgraded it appropriately. Hopefully, it will recover soon.
In summary, I feel terribly foolish, but at least I will never come so close to disaster again. I should probably register it for the next five years, or similar. And remember, check on the status of all your domains today!
Well, I'll admit it, I panicked. I mean, what exactly does happen when a domain expires? It's actually happened to me before, but that was before ICANN brought in their rather scary sounding 'redemption' thing.
Redemption, it turns out, is a process where the registrar, apparently, may or may not let you have your domain back on payment of a stupid fee; $160 for Enom (who Hosting365 resells for). That's right, they can if they feel like it, but they don't seem to be under any obligation to. Given the nature of the site, I'd be quite willing to pay, but it was a little scary that they might just refuse.As it turns out, all was well. My domain was actually in some odd state between registered and redemption, and I was able to re-register through the normal process, for the normal price, with the help of nice helpful people at Hosting365. I know they get a fair bit of flack, but in this case at least they seemed to be quite reasonable.
The site should be back now, for most people. Interestingly, the Google AdSense price per thousand impressions for it is currently horrible; I assume their robots visited while it was a holding page and downgraded it appropriately. Hopefully, it will recover soon.
In summary, I feel terribly foolish, but at least I will never come so close to disaster again. I should probably register it for the next five years, or similar. And remember, check on the status of all your domains today!
Labels:
adsense,
domains,
findmeatune,
hosting365,
Technology
Belarus wants to build a nuclear power plant
While I'm all for normal countries building nuclear power plants, I can't help thinking it may not be a good idea for Europe's one remaining wacky dictatorship to do so.
Of course, the wacky dictator has said that they want to build one; convincing someone to actually build one for them will probably be quite another matter. I can't see Russia doing it, for instance.
Of course, the wacky dictator has said that they want to build one; convincing someone to actually build one for them will probably be quite another matter. I can't see Russia doing it, for instance.
Labels:
"nuclear energy",
belarus,
dictators,
Nuclear
Making a Cocoa Interface for Common Lisp
A few days ago I was talking about PyObjC, the Python interface layer for Objective C. With PyObjC, you can write Python applications for MacOS with Cocoa interfaces.
I was also recently talking about Common Lisp's issues with user interfaces. Cocoa is particularly problematic; it's supported by OpenMCL, but OpenMCL is only available for PowerPC and 64-bit Intel Macs. Even on 64 bit Intel Macs, you can't use Cocoa, because the current version of MacOS doesn't let 64 bit processes use Cocoa; this is apparently due to change with Leopard.
LTK is a Lisp GUI library which provides a Tcl interface by using a slave Tk process.
I thought I'd try something vaguely similar with PyObjC, so I wrote a simple UI with Python and PyObjC, which is run by Lisp, communicating with it through standard input and output. At the moment the level of communication is extremely primitive, but there's no reason that the Lisp process couldn't launch an empty PyObjC slave application, and create an interface programmatically within it, or use an interface produced in Interface Builder. This is what I'd like to work towards, if and when I have the time. Ultimately, an application written this way could probably be distributed as a MacOS application bundle, which would be nice.
The way I'm currently doing it, the Python side listens on stdin using NSFileHandle's input notifications, while the Lisp side constantly waits on input, only doing something when it receives data. I'm not sure that this is the best approach.
Anyway, here are the Lisp and Python side. The Lisp end works on at least OpenMCL and clisp.
I was also recently talking about Common Lisp's issues with user interfaces. Cocoa is particularly problematic; it's supported by OpenMCL, but OpenMCL is only available for PowerPC and 64-bit Intel Macs. Even on 64 bit Intel Macs, you can't use Cocoa, because the current version of MacOS doesn't let 64 bit processes use Cocoa; this is apparently due to change with Leopard.
LTK is a Lisp GUI library which provides a Tcl interface by using a slave Tk process.
I thought I'd try something vaguely similar with PyObjC, so I wrote a simple UI with Python and PyObjC, which is run by Lisp, communicating with it through standard input and output. At the moment the level of communication is extremely primitive, but there's no reason that the Lisp process couldn't launch an empty PyObjC slave application, and create an interface programmatically within it, or use an interface produced in Interface Builder. This is what I'd like to work towards, if and when I have the time. Ultimately, an application written this way could probably be distributed as a MacOS application bundle, which would be nice.
The way I'm currently doing it, the Python side listens on stdin using NSFileHandle's input notifications, while the Lisp side constantly waits on input, only doing something when it receives data. I'm not sure that this is the best approach.
Anyway, here are the Lisp and Python side. The Lisp end works on at least OpenMCL and clisp.
Labels:
"common lisp",
"objective c",
cocoa,
lisp,
pyObjC,
python,
ui
Sunday, October 14, 2007
The problem with commercial Common Lisp
Update: Followup here.
There are a number of free multi-platform Common Lisp implementations, but very few which work on all major platforms with full features, and, as far as I know, none which provide threading on all platforms. In addition, there is no properly cross-platform UI library for Common Lisp, though some of the GTK ones and LTK come close; LTK is a library which allows interaction with a separate Tcl/Tk process, and will thus work on any platform supporting Tk, but for Windows at least you do have to install Tk yourself, while GTK is only marginally available for MacOS X, and distribution of MacOS GTK programs is awkward.
Of course, this isn't a problem if you just want to write programs yourself, or if you just want to write webapps. If you want to write applications which can be used by people with limited computer knowledge, though... well, that's not so easy.
At first glance, it would seem that one of the commercial Lisp implementations would be helpful. At the moment, there are two big players, Allegro and Lispworks. Allegro is out, for my purposes (creating distributable applications which run on at least Windows and MacOS); it doesn't really do MacOS GUI stuff, and also charges per-user. Lispworks looks more promising; it has a cross-platform GUI system which works on Windows, MacOS, and Linux, and it is able to produce distributable applications of relatively small size, through a technique called tree-shaking. It supports MacOS on PPC and Intel, and does Unicode and threading and so on. Sounds great, right?
There's a problem, though. First off, LispWorks is expensive. The standard edition costs 1200 euro, or 1300 dollars. We get hurt on the exchange rate, but it's even quite expensive for Americans. Now, of course, that sort of money is small change for a company, and many other programming tools, like Visual Studio, are of comparable cost. For a hobbyist, though it's no joke.
There's another, bigger problem, though. The price by itself might be manageable if that was all there was to it... These days, if you're producing a distributable application, it's likely that you'll want it to run on Windows, and PPC and Intel MacOS. It turns out, though, that to do this with LispWorks you will need both a Windows and a MacOS copy. Fortunately, the Intel MacOS copy can produce PPC MacOS binaries by running in Rosetta, so you don't need three copies, but that's still 2400 euro. If you want to produce Linux output as well, you'll need yet another copy.
I can understand that there are technical reasons why making a cross-compiler would be difficult or impossible; Lisp distributables are generally made by saving a copy of the environment's memory, more or less. However, it would seem to make a lot of sense to bundle a Windows tool to make distributable apps with the MacOS version, and vice versa. Alternatively, they could provide users who had bought a copy with a website where they could upload their code and get an application back.
Neither of these would cost them much, and I think that this would make the whole thing far more appealing to hobbyists, who, when it comes to it, are not likely to be making much or anything with their applications. Of course, possibly they just want to target businesses, but I would have though that it would make sense for them to encourage hobbyists to use their products; if nothing else, some of those hobbyists might go on to start working with the platform commercially, and one sale is presumably better than none. I could just about justify buying one copy, but it seems absurd to have to buy two just to produce distributable files for both major platforms.
Update: Followup here.
There are a number of free multi-platform Common Lisp implementations, but very few which work on all major platforms with full features, and, as far as I know, none which provide threading on all platforms. In addition, there is no properly cross-platform UI library for Common Lisp, though some of the GTK ones and LTK come close; LTK is a library which allows interaction with a separate Tcl/Tk process, and will thus work on any platform supporting Tk, but for Windows at least you do have to install Tk yourself, while GTK is only marginally available for MacOS X, and distribution of MacOS GTK programs is awkward.
Of course, this isn't a problem if you just want to write programs yourself, or if you just want to write webapps. If you want to write applications which can be used by people with limited computer knowledge, though... well, that's not so easy.
At first glance, it would seem that one of the commercial Lisp implementations would be helpful. At the moment, there are two big players, Allegro and Lispworks. Allegro is out, for my purposes (creating distributable applications which run on at least Windows and MacOS); it doesn't really do MacOS GUI stuff, and also charges per-user. Lispworks looks more promising; it has a cross-platform GUI system which works on Windows, MacOS, and Linux, and it is able to produce distributable applications of relatively small size, through a technique called tree-shaking. It supports MacOS on PPC and Intel, and does Unicode and threading and so on. Sounds great, right?
There's a problem, though. First off, LispWorks is expensive. The standard edition costs 1200 euro, or 1300 dollars. We get hurt on the exchange rate, but it's even quite expensive for Americans. Now, of course, that sort of money is small change for a company, and many other programming tools, like Visual Studio, are of comparable cost. For a hobbyist, though it's no joke.
There's another, bigger problem, though. The price by itself might be manageable if that was all there was to it... These days, if you're producing a distributable application, it's likely that you'll want it to run on Windows, and PPC and Intel MacOS. It turns out, though, that to do this with LispWorks you will need both a Windows and a MacOS copy. Fortunately, the Intel MacOS copy can produce PPC MacOS binaries by running in Rosetta, so you don't need three copies, but that's still 2400 euro. If you want to produce Linux output as well, you'll need yet another copy.
I can understand that there are technical reasons why making a cross-compiler would be difficult or impossible; Lisp distributables are generally made by saving a copy of the environment's memory, more or less. However, it would seem to make a lot of sense to bundle a Windows tool to make distributable apps with the MacOS version, and vice versa. Alternatively, they could provide users who had bought a copy with a website where they could upload their code and get an application back.
Neither of these would cost them much, and I think that this would make the whole thing far more appealing to hobbyists, who, when it comes to it, are not likely to be making much or anything with their applications. Of course, possibly they just want to target businesses, but I would have though that it would make sense for them to encourage hobbyists to use their products; if nothing else, some of those hobbyists might go on to start working with the platform commercially, and one sale is presumably better than none. I could just about justify buying one copy, but it seems absurd to have to buy two just to produce distributable files for both major platforms.
Update: Followup here.
Labels:
"common lisp",
allegro,
lisp,
lispworks
Saturday, October 13, 2007
Fun with Lidl
Lidl is a cheapish German supermarket chain which has a good few branches in Dublin these days. I was in my local one today, and saw a few interesting things.
First, a brand of cake mixes called 'Landgut'. I'm not sure why that is funny, but it is. Say it yourself. 'Landgut'. There.
Next, toilet rolls, with a cat on the bag. The brand? 'Passé'. For people who always have to have the newest thing, there's also non-Passé toilet paper, apparently cat-free.
I could go on about absurd Lidl items all day, but I'll restrain myself to one more. Every week, they have a special offer on a random item, like laptops or welding equipment or tents (really!). This week, there's a special offer on... FRUIT TREES. You can get a fruit tree for about six euro, which is, I suppose, quite good value, but hardly the most likely impulse purchase.
First, a brand of cake mixes called 'Landgut'. I'm not sure why that is funny, but it is. Say it yourself. 'Landgut'. There.
Next, toilet rolls, with a cat on the bag. The brand? 'Passé'. For people who always have to have the newest thing, there's also non-Passé toilet paper, apparently cat-free.
I could go on about absurd Lidl items all day, but I'll restrain myself to one more. Every week, they have a special offer on a random item, like laptops or welding equipment or tents (really!). This week, there's a special offer on... FRUIT TREES. You can get a fruit tree for about six euro, which is, I suppose, quite good value, but hardly the most likely impulse purchase.
Labels:
fruit,
lidl,
random,
silly,
supermarket
Wednesday, October 10, 2007
Programmatically creating Apple Cocoa UIs with PyObjC, a Python Objective C Binding
PyObjC is a library which allows you to access Objejctive C (and in particular Cocoa, Apple's current favoured GUI API) from Python. Most people using PyObjC to put together an application with a user interface will use Interface Builder, Apple's favoured, well, interface building tool. For reasons which I'll go into later, though, I'd like to be able to create user interfaces programmatically.
Oddly, there seems to be very little information on how to do this (or indeed on how to create Cocoa applications programmatically with Objective C) out there. Apparently, at least one book on Cocoa does cover it, but in my case I had to figure it out for myself. I can see that most people would want to use simple GUI tools, but it's surprising that no-one seems to have gone down the other road.
First, you must install PyObjC. From the documentation, unfortunately, it is not quite obvious how to do this; the installer demands 'System Python 2.4', while MacOS comes with 2.3. There are versions for 2.3, but they don't seem to do universal binaries. It turns out, though, that the Python 2.4 (but not 2.5) package from PythonMac will do just fine. Install that, and you'll be able to install PyObjC.
PyObjC is pretty nice. It gives you access to most Cocoa functions, and also comes with an application called py2app, which takes your project and makes a distributable MacOS application (a Universal Binary one, at that!) from it. It includes all the bits of Python and PyObjC required to run the application. On my machine, a simple application worked out to roughly seven megabytes, compressed, which isn't too bad by modern standards.
Without further ado, then, here is the (commented) code. It's not at all well-written, and the UI produced is hideous, but it does give you an idea of how to get started.
Oddly, there seems to be very little information on how to do this (or indeed on how to create Cocoa applications programmatically with Objective C) out there. Apparently, at least one book on Cocoa does cover it, but in my case I had to figure it out for myself. I can see that most people would want to use simple GUI tools, but it's surprising that no-one seems to have gone down the other road.
First, you must install PyObjC. From the documentation, unfortunately, it is not quite obvious how to do this; the installer demands 'System Python 2.4', while MacOS comes with 2.3. There are versions for 2.3, but they don't seem to do universal binaries. It turns out, though, that the Python 2.4 (but not 2.5) package from PythonMac will do just fine. Install that, and you'll be able to install PyObjC.
PyObjC is pretty nice. It gives you access to most Cocoa functions, and also comes with an application called py2app, which takes your project and makes a distributable MacOS application (a Universal Binary one, at that!) from it. It includes all the bits of Python and PyObjC required to run the application. On my machine, a simple application worked out to roughly seven megabytes, compressed, which isn't too bad by modern standards.
Without further ado, then, here is the (commented) code. It's not at all well-written, and the UI produced is hideous, but it does give you an idea of how to get started.
from Foundation import *
from AppKit import *
global app
def makeApp():
global app
app = NSApplication.sharedApplication()
# Handle window events
class WindowDelegate(NSObject):
def windowWillClose_(self, notification):
print "Bye, now"
app.terminate_(self)
# Handle events from our button
class ButtonHandler(NSObject):
# rest could contain event info, but is empty here
def doSomething(self, *rest):
global button
print 'Button Pressed!'
# Toggle button's border on and off
button.setBordered_(not button.isBordered())
def makeWindow():
global app
graphicsRect = NSMakeRect(100.0, 350.0, 450.0, 400.0)
#Make window
myWindow = NSWindow.alloc().initWithContentRect_styleMask_backing_defer_(
graphicsRect,
NSTitledWindowMask |
NSClosableWindowMask |
NSResizableWindowMask |
NSMiniaturizableWindowMask,
NSBackingStoreBuffered, False)
# Set handler for window
myWindow.setTitle_("PyObjC Example")
myDelegate = WindowDelegate.alloc( ).init( )
myWindow.setDelegate_(myDelegate)
# Create button, and add to window
global button
button = NSButton.alloc().init()
myWindow.contentView().addSubview_(button)
button.setFrame_(NSMakeRect(10.0, 35.0, 45.0, 40.0))
# Set handler for button; note that the function is called by string name
buttonHandler = ButtonHandler.alloc().init()
button.setTarget_(buttonHandler)
button.setAction_("doSomething")
# Display window
myWindow.display()
myWindow.orderFrontRegardless()
# Start application
app.run()
def go():
global app
makeApp()
makeWindow()
if __name__ == '__main__':
go()
Tuesday, October 9, 2007
Highly useful MacOS program
I decided to give MacOS's XCode thing a go. This is what I came up with; it's a little desktop application to en/decrypt ROT13, an oh-so-secure code used occasionally by annoying USENet people.
V'yy serryl nqzvg gung vg'f n ovg njshy, ohg ng yrnfg lbh pna hfr vg gb ernq guvf frperg zrffntr.
KPbqr vf sha!
You can get it here, if you like. It's a Universal binary, and everything!
V'yy serryl nqzvg gung vg'f n ovg njshy, ohg ng yrnfg lbh pna hfr vg gb ernq guvf frperg zrffntr.
KPbqr vf sha!
Labels:
fun,
macos,
Programming,
rot13,
Technology,
usenet,
xcode
Friday, October 5, 2007
Doing more with Hunchentoot, the Common Lisp webserver - a tiny macro-based framework
(Update: The application is online here.)
In a previous article, I gave a very simple introduction to using Hunchentoot, Edi Weitz's Common Lisp webserver. As someone noted in the Reddit comments, it was not really a wonderful article, and didn't tell you much that you couldn't get easily from the documentation.
Now, before I start, I should note that the sort of thing I'm going to be doing is very much personal taste, and may not suit your development style at all. I'm not even sure that it suits mine that well, but it's just a little introduction to give you a feel for the sorts of things that you can do easily in Lisp, and how they relate to writing a web application.
I'm going to be writing a simple, pointless webapp with user login. For this, I will put together a very simple framework. Frameworks are, of course, very popular in the web development world, and tend, by and large, to be vast, constraining things suited for writing a particular type of application. I hope to show here that it is easy, in Lisp, to put together a simple framework to suit your application's needs as you write it.
I'm going to be using macros here. Macros in Common Lisp are quite sophisticated creatures; they are effectively functions which produce code at runtime. I'll also be using a couple of nice Hunchentoot features that I didn't mention the first time around; sessions, which let you store data associated with a web client, and hunchentoot-mp, a set of cross-platform functions for dealing with multi-processing which comes with Hunchentoot. I'll also be using cl-who, a HTML generation library.
Hunchentoot sessions work by giving the client a session-identifying cookie, and storing the session data itself in memory on the webserver. This approach has its limitations; in particular, it is problematic if you are using more than one webserver; however, it is fine for a simple application like this. The multi-processing functions are handy because the Hunchentoot server runs as a series of Lisp processes (threads) in a single Lisp environment; if we need to store non-user-specific data in memory, we have to prevent two users attempting to write to it at the same time.
The web application consists of an authentication-protected site where users can perform a variety of actions depending on their assigned roles. Actions will set statuses, much like on Facebook; they'll be stored in a mutex-locked list. Users will be able to view just their own statuses, or everyone's. Each user can have more than one role, and each role can require one or more roles. As it's just a little example, there'll be no persistence; user accounts will be defined in a list, and statuses will be stored in memory. In a real application of this type, of course, you'd probably use a database or other persistent store.
It would be convenient to have details on the current user from the session available in each page handler. One way to handle this would be to just do a (let ((user-details ...))) enclosing the handler contents, but the way I'm going to do it is writing a macro with syntax (with-user-details (user-details) ...). There's not much obvious gain from this in this instance, but it looks a bit nicer, and it would be convenient if more work ever had to be done in that session data retrieval bit. For instance, you might want to add a line to a log every time a logged-in user viewed a page, or do something with the result of the page generation function.
I'd also like to be able to have certain parts of the page only displayed for registered users with the correct roles. For this, I'll use another little macro. (display-with-roles user-details list-of-roles ...)
I'd also like to use a basic page template. Again, this can be nicely implemented in a macro, and then the display code for a given page wrapped in (with-template ...).
Finally, it looks like there's a fair bit of functionality every page is going to require. Page handlers are functions, but there's no reason that we have to define them using plain old defun. Instead, we have yet another macro, defpage. This macro wraps the code provided to it in the with-user-details macro, and the with-template macro. It also puts in code to check that the user is allowed to view the page at all, and adds the page handler to hunchentoot's dispatch table. (defpage page-name url roles-required ..).
I'll also have a (defevent page-name url roles-required text). This will just create a page which performs an event.
So, without further ado, here's the code. It's not perfect, and for this small application it's possibly a bit long, but it does make it easier to write apps of this sort. You can try it out here.
(defpackage :demoserv2
(:use :hunchentoot
:cl-who
:cl)
(:export
:start-server))
(in-package :demoserv2)
(setq *dispatch-table* (list (create-regex-dispatcher "^/login$" 'login)))
(defparameter *users* '(("mags" "icecream" (:scary-hair :evil))
("annie" "mrpugwash" (:ann-widdicombe))
("tony" "gwbush" (:evil :boring :scary-wife))
("john" "grey" (:boring))))
(defvar *our-mutex* (hunchentoot-mp:make-lock "our-lock"))
(defvar *events* '())
; Add event to log
(defun add-event (user text)
(hunchentoot-mp:with-lock (*our-mutex*)
(push `(,user ,text) *events*)))
; Does this user have access to these roles?
(defun has-access (user-details required-roles)
(reduce #'(lambda (a b) (and a b))
(mapcar #'(lambda (role) (find role (second user-details)))
required-roles)))
; Get user details from session
(defmacro with-user-details (user-details &body body)
`(let ((,user-details (session-value :user)))
,@body))
; Display body if the user is allowed access it
(defmacro display-with-roles (user-details required-roles &body body)
`(if (and user-details (has-access ,user-details ,required-roles))
,@body))
; Template
(defmacro with-template (title user-details &body body)
`(with-html-output-to-string (*standard-output*)
(:html
(:head (:title (fmt "Hunchentoot demo - ~a" ,title)))
(:body (:h1 "Hunchentoot Demo")
(:div (:a :href "/" "Menu") " - "
(:a :href "/events" "Events")
(if user-details
(htm " - " (:a :href "/logout" "Logout"))))
,@body))))
; Defines a normal page with basic infrastructure
(defmacro defpage (name url required-roles user-details &body body)
`(progn
(defun ,name ()
(with-user-details ,user-details
(if (or (not ,required-roles)
(has-access ,user-details ,required-roles))
(progn ,@body)
"You're not allowed view this page")))
(push (create-regex-dispatcher ,(format nil "^/~a$" url) ',name)
*dispatch-table*)))
; Defines an event page
(defmacro defevent (name url required-roles text)
`(defpage ,name ,url ,required-roles user-details
(add-event (first user-details) ,text)
(with-template ,text user-details
(htm (:h3 "Event Registered") (str ,text)))))
; Index
(defpage index-page "" nil user-details
(with-template "Index" user-details
(:div
(if (not user-details)
(htm (:form :action "/login" :method "post"
"Username:" (:input :type "text" :name "username")
"Password:" (:input :type "password" :name "password")
(:input :type "submit" :value "submit"))))
(htm (:ul
(display-with-roles user-details '(:scary-hair)
(htm (:li (:a :href "/impose-poll-tax" "Impose Poll Tax"))))
(display-with-roles user-details '(:evil)
(htm (:li (:a :href "/have-a-war" "Have a Nice War"))))
(display-with-roles user-details '(:scary-hair :evil)
(htm (:li (:a :href "/ice-cream" "Invent Soft Ice-cream"))))
(display-with-roles user-details '(:boring)
(htm (:li (:a :href "/eat-peas" "Eat Peas"))))
(display-with-roles user-details '(:scary-wife)
(htm (:li (:a :href "/flats" "Have Flat Investment Scandal"))))
(display-with-roles user-details '(:ann-widdicombe)
(htm (:li (:a :href "/celeb-fat" "Go on Celebrity Fat Farm")))))))))
; Events
(defevent impose-poll-tax "impose-poll-tax" '(:scary-hair) "Poll tax imposed!")
(defevent celeb-fat "celeb-fat" '(:ann-widdicombe) "Went to Celebrity Fat Farm!")
(defevent have-a-war "have-a-war" '(:evil) "Had a nice war. I do like those.")
(defevent ice-cream "ice-cream" '(:evil :scary-hair) "Invented soft ice-cream.")
(defevent eat-peas "eat-peas" '(:boring) "Peas are nice, dear!")
(defevent flats "flats" '(:scary-wife) "Buy flat through fraudster.")
(defpage logout "logout" nil user-details
(delete-session-value :user)
(redirect "/"))
; Events listing
(defpage events "events" nil user-details
(let* ((user (parameter "user"))
(our-events (if user
(remove-if-not #'(lambda (a) (equal user a))
(hunchentoot-mp:with-lock (*our-mutex*)
*events*)
:key #'first)
(hunchentoot-mp:with-lock (*our-mutex*)
*events*))))
(with-template "Events" user-details
(htm (:h3 "Events")
(:ul
(dolist (i our-events)
(htm (:li (:a :href (format nil "/events?user=~a" (first i)) (str (first i))) " - "
(str (second i))))))))))
(defun login ()
(let ((username (parameter "username"))
(password (parameter "password")))
(let ((user-details (find username *users* :test #'equal :key #'first)))
(if user-details
(cond ((equal (second user-details) password)
(start-session)
(setf (session-value :user) (list (first user-details)
(third user-details)))
(redirect "/"))
(t "Bad password"))
"Non-existent user"))))
In a previous article, I gave a very simple introduction to using Hunchentoot, Edi Weitz's Common Lisp webserver. As someone noted in the Reddit comments, it was not really a wonderful article, and didn't tell you much that you couldn't get easily from the documentation.
Now, before I start, I should note that the sort of thing I'm going to be doing is very much personal taste, and may not suit your development style at all. I'm not even sure that it suits mine that well, but it's just a little introduction to give you a feel for the sorts of things that you can do easily in Lisp, and how they relate to writing a web application.
I'm going to be writing a simple, pointless webapp with user login. For this, I will put together a very simple framework. Frameworks are, of course, very popular in the web development world, and tend, by and large, to be vast, constraining things suited for writing a particular type of application. I hope to show here that it is easy, in Lisp, to put together a simple framework to suit your application's needs as you write it.
I'm going to be using macros here. Macros in Common Lisp are quite sophisticated creatures; they are effectively functions which produce code at runtime. I'll also be using a couple of nice Hunchentoot features that I didn't mention the first time around; sessions, which let you store data associated with a web client, and hunchentoot-mp, a set of cross-platform functions for dealing with multi-processing which comes with Hunchentoot. I'll also be using cl-who, a HTML generation library.
Hunchentoot sessions work by giving the client a session-identifying cookie, and storing the session data itself in memory on the webserver. This approach has its limitations; in particular, it is problematic if you are using more than one webserver; however, it is fine for a simple application like this. The multi-processing functions are handy because the Hunchentoot server runs as a series of Lisp processes (threads) in a single Lisp environment; if we need to store non-user-specific data in memory, we have to prevent two users attempting to write to it at the same time.
The web application consists of an authentication-protected site where users can perform a variety of actions depending on their assigned roles. Actions will set statuses, much like on Facebook; they'll be stored in a mutex-locked list. Users will be able to view just their own statuses, or everyone's. Each user can have more than one role, and each role can require one or more roles. As it's just a little example, there'll be no persistence; user accounts will be defined in a list, and statuses will be stored in memory. In a real application of this type, of course, you'd probably use a database or other persistent store.
It would be convenient to have details on the current user from the session available in each page handler. One way to handle this would be to just do a (let ((user-details ...))) enclosing the handler contents, but the way I'm going to do it is writing a macro with syntax (with-user-details (user-details) ...). There's not much obvious gain from this in this instance, but it looks a bit nicer, and it would be convenient if more work ever had to be done in that session data retrieval bit. For instance, you might want to add a line to a log every time a logged-in user viewed a page, or do something with the result of the page generation function.
I'd also like to be able to have certain parts of the page only displayed for registered users with the correct roles. For this, I'll use another little macro. (display-with-roles user-details list-of-roles ...)
I'd also like to use a basic page template. Again, this can be nicely implemented in a macro, and then the display code for a given page wrapped in (with-template ...).
Finally, it looks like there's a fair bit of functionality every page is going to require. Page handlers are functions, but there's no reason that we have to define them using plain old defun. Instead, we have yet another macro, defpage. This macro wraps the code provided to it in the with-user-details macro, and the with-template macro. It also puts in code to check that the user is allowed to view the page at all, and adds the page handler to hunchentoot's dispatch table. (defpage page-name url roles-required ..).
I'll also have a (defevent page-name url roles-required text). This will just create a page which performs an event.
So, without further ado, here's the code. It's not perfect, and for this small application it's possibly a bit long, but it does make it easier to write apps of this sort. You can try it out here.
(defpackage :demoserv2
(:use :hunchentoot
:cl-who
:cl)
(:export
:start-server))
(in-package :demoserv2)
(setq *dispatch-table* (list (create-regex-dispatcher "^/login$" 'login)))
(defparameter *users* '(("mags" "icecream" (:scary-hair :evil))
("annie" "mrpugwash" (:ann-widdicombe))
("tony" "gwbush" (:evil :boring :scary-wife))
("john" "grey" (:boring))))
(defvar *our-mutex* (hunchentoot-mp:make-lock "our-lock"))
(defvar *events* '())
; Add event to log
(defun add-event (user text)
(hunchentoot-mp:with-lock (*our-mutex*)
(push `(,user ,text) *events*)))
; Does this user have access to these roles?
(defun has-access (user-details required-roles)
(reduce #'(lambda (a b) (and a b))
(mapcar #'(lambda (role) (find role (second user-details)))
required-roles)))
; Get user details from session
(defmacro with-user-details (user-details &body body)
`(let ((,user-details (session-value :user)))
,@body))
; Display body if the user is allowed access it
(defmacro display-with-roles (user-details required-roles &body body)
`(if (and user-details (has-access ,user-details ,required-roles))
,@body))
; Template
(defmacro with-template (title user-details &body body)
`(with-html-output-to-string (*standard-output*)
(:html
(:head (:title (fmt "Hunchentoot demo - ~a" ,title)))
(:body (:h1 "Hunchentoot Demo")
(:div (:a :href "/" "Menu") " - "
(:a :href "/events" "Events")
(if user-details
(htm " - " (:a :href "/logout" "Logout"))))
,@body))))
; Defines a normal page with basic infrastructure
(defmacro defpage (name url required-roles user-details &body body)
`(progn
(defun ,name ()
(with-user-details ,user-details
(if (or (not ,required-roles)
(has-access ,user-details ,required-roles))
(progn ,@body)
"You're not allowed view this page")))
(push (create-regex-dispatcher ,(format nil "^/~a$" url) ',name)
*dispatch-table*)))
; Defines an event page
(defmacro defevent (name url required-roles text)
`(defpage ,name ,url ,required-roles user-details
(add-event (first user-details) ,text)
(with-template ,text user-details
(htm (:h3 "Event Registered") (str ,text)))))
; Index
(defpage index-page "" nil user-details
(with-template "Index" user-details
(:div
(if (not user-details)
(htm (:form :action "/login" :method "post"
"Username:" (:input :type "text" :name "username")
"Password:" (:input :type "password" :name "password")
(:input :type "submit" :value "submit"))))
(htm (:ul
(display-with-roles user-details '(:scary-hair)
(htm (:li (:a :href "/impose-poll-tax" "Impose Poll Tax"))))
(display-with-roles user-details '(:evil)
(htm (:li (:a :href "/have-a-war" "Have a Nice War"))))
(display-with-roles user-details '(:scary-hair :evil)
(htm (:li (:a :href "/ice-cream" "Invent Soft Ice-cream"))))
(display-with-roles user-details '(:boring)
(htm (:li (:a :href "/eat-peas" "Eat Peas"))))
(display-with-roles user-details '(:scary-wife)
(htm (:li (:a :href "/flats" "Have Flat Investment Scandal"))))
(display-with-roles user-details '(:ann-widdicombe)
(htm (:li (:a :href "/celeb-fat" "Go on Celebrity Fat Farm")))))))))
; Events
(defevent impose-poll-tax "impose-poll-tax" '(:scary-hair) "Poll tax imposed!")
(defevent celeb-fat "celeb-fat" '(:ann-widdicombe) "Went to Celebrity Fat Farm!")
(defevent have-a-war "have-a-war" '(:evil) "Had a nice war. I do like those.")
(defevent ice-cream "ice-cream" '(:evil :scary-hair) "Invented soft ice-cream.")
(defevent eat-peas "eat-peas" '(:boring) "Peas are nice, dear!")
(defevent flats "flats" '(:scary-wife) "Buy flat through fraudster.")
(defpage logout "logout" nil user-details
(delete-session-value :user)
(redirect "/"))
; Events listing
(defpage events "events" nil user-details
(let* ((user (parameter "user"))
(our-events (if user
(remove-if-not #'(lambda (a) (equal user a))
(hunchentoot-mp:with-lock (*our-mutex*)
*events*)
:key #'first)
(hunchentoot-mp:with-lock (*our-mutex*)
*events*))))
(with-template "Events" user-details
(htm (:h3 "Events")
(:ul
(dolist (i our-events)
(htm (:li (:a :href (format nil "/events?user=~a" (first i)) (str (first i))) " - "
(str (second i))))))))))
(defun login ()
(let ((username (parameter "username"))
(password (parameter "password")))
(let ((user-details (find username *users* :test #'equal :key #'first)))
(if user-details
(cond ((equal (second user-details) password)
(start-session)
(setf (session-value :user) (list (first user-details)
(third user-details)))
(redirect "/"))
(t "Bad password"))
"Non-existent user"))))
Labels:
"common lisp",
"web application",
facebook,
hunchentoot,
lisp,
Programming,
Technology,
tutorial,
Tutorials
Thursday, October 4, 2007
Reddit Adventure
Last week, I wrote a very quick, not very good, introduction to using Hunchentoot, a Common Lisp webserver. And, amazingly, it jumped to the top of programming.reddit.com! I suspect that it was helped in this by a mention from Xach. Admittedly, it was a Sunday, so the competition was in all probability not very fierce. Still quite surprising, though.
Lessons learned? It turns out that people on Reddit tend not to actually read the articles, as you can see from the comment page. I was asked why I didn't use cl-who, which was mentioned in the article; I was informed that I wrote cl-who, which is patently false, and so forth. Someone also correctly mentioned that it wasn't much of a tutorial. ;)
Anyway, I am currently, when I get a chance, working on a couple of sequels; one on how to make an application-centric mini-framework with macros (it uses cl-who and everything, which should make that commenter happy; Update: it's here), and one on how to write a Facebook application with Hunchentoot. The availability of macros is, I believe, one of the aspects of Lisp that makes it particularly handy for writing web applications.
Ironically, it is highly unlikely that either of these slightly more interesting articles will get anything like the same attention as the original. Damn, branded as a writer of mediocre tutorials! :)
By the way, being briefly top on programming.reddit.com on a Sunday gets you roughly 2,000 visits. So now you know.
Lessons learned? It turns out that people on Reddit tend not to actually read the articles, as you can see from the comment page. I was asked why I didn't use cl-who, which was mentioned in the article; I was informed that I wrote cl-who, which is patently false, and so forth. Someone also correctly mentioned that it wasn't much of a tutorial. ;)
Anyway, I am currently, when I get a chance, working on a couple of sequels; one on how to make an application-centric mini-framework with macros (it uses cl-who and everything, which should make that commenter happy; Update: it's here), and one on how to write a Facebook application with Hunchentoot. The availability of macros is, I believe, one of the aspects of Lisp that makes it particularly handy for writing web applications.
Ironically, it is highly unlikely that either of these slightly more interesting articles will get anything like the same attention as the original. Damn, branded as a writer of mediocre tutorials! :)
By the way, being briefly top on programming.reddit.com on a Sunday gets you roughly 2,000 visits. So now you know.
Labels:
"common lisp",
"web application",
hunchentoot,
lisp,
reddit,
Technology,
tutorial
Tuesday, October 2, 2007
Monster.ie apparently, well, sending unsolicited email
Monster, the big weird recruitment agency, is apparently spamming various people involved in IT@Cork. And someone with a Monster IP address (but who doesn't work for Monster, dear me, no) is jumping in to defend them. Shills should at least use proxies.
Oh, and the idiot who sent it included all the recipient emails in the To field, so that they could all see each other. Clever of him. Nice to know that they're not just sociopaths, they're incompetent too.
Coverage here, here, and here. Charming, eh?
Oh, and the idiot who sent it included all the recipient emails in the To field, so that they could all see each other. Clever of him. Nice to know that they're not just sociopaths, they're incompetent too.
Coverage here, here, and here. Charming, eh?
Labels:
monster,
Reputable Businessmen,
spam
Doing more with Hunchentoot (Common Lisp webserver)
In a previous article, I gave a very simple introduction to using Hunchentoot, Edi Weitz's Common Lisp webserver. As someone noted in the Reddit comments, it was not really a wonderful article, and didn't tell you much that you couldn't get easily from the documentation.
Now, before I start, I should note that the sort of thing I'm going to be doing is very much personal taste, and may not suit your development style at all. I'm not even sure that it suits mine that well, but it's just a little introduction to give you a feel for the sorts of things that you can do easily in Lisp, and how they relate to writing a web application.
I'm going to be writing a simple, pointless webapp with user login. For this, I will put together a very simple framework. Frameworks are, of course, very popular in the web development world, and tend, by and large, to be vast, constraining things suited for writing a particular type of application. I hope to show here that it is easy, in Lisp, to put together a simple framework to suit your application's needs as you write it.
I'm going to be using macros here. Macros in Common Lisp are quite sophisticated creatures; they are effectively functions which produce code at runtime. I'll also be using a couple of nice Hunchentoot features that I didn't mention the first time around; sessions, which let you store data associated with a web client, and hunchentoot-mp, a set of cross-platform functions for dealing with multi-processing which comes with Hunchentoot. I'll also be using cl-who, a HTML generation library.
Hunchentoot sessions work by giving the client a session-identifying cookie, and storing the session data itself in memory on the webserver. This approach has its limitations; in particular, it is problematic if you are using more than one webserver; however, it is fine for a simple application like this. The multi-processing functions are handy because the Hunchentoot server runs as a series of Lisp processes (threads) in a single Lisp environment; if we need to store non-user-specific data in memory, we have to prevent two users attempting to write to it at the same time.
The web application consists of an authentication-protected site where users can perform a variety of actions depending on their assigned roles. Actions will set statuses, much like on Facebook; they'll be stored in a mutex-locked list. Users will be able to view just their own statuses, or everyone's. Each user can have more than one role, and each role can require one or more roles. As it's just a little example, there'll be no persistence; user accounts will be defined in a list, and statuses will be stored in memory. In a real application of this type, of course, you'd probably use a database or other persistent store.
It would be convenient to have details on the current user from the session available in each page handler. One way to handle this would be to just do a (let ((user-details ...)))enclosing the handler contents, but the way I'm going to do it is writing a macro with syntax (with-user-details (user-details) ...). There's not much obvious gain from this in this instance, but it looks a bit nicer, and it would be convenient if more work ever had to be done in that session data retrieval bit. For instance, you might want to add a line to a log every time a logged-in user viewed a page, or do something with the result of the page generation function.
I'd also like to be able to have certain parts of the page only displayed for registered users with the correct roles. For this, I'll use another little macro. (display-with-roles user-details list-of-roles ...)
I'd also like to use a basic page template. Again, this can be nicely implemented in a macro, and then the display code for a given page wrapped in (with-template ...).
Finally, it looks like there's a fair bit of functionality every page is going to require. Page handlers are functions, but there's no reason that we have to define them using plain old defun. Instead, we have yet another macro, defpage. This macro wraps the code provided to it in the with-user-details macro, and the with-template macro. It also puts in code to check that the user is allowed to view the page at all, and adds the page handler to hunchentoot's dispatch table. (defpage page-name url roles-required ..).
I'll also have a (defevent page-name url roles-required text). This will just create a page which performs an event.
So, without further ado, here's the code. It's not perfect, and for this small application it's possibly a bit long, but it does make it easier to write apps of this sort. You can try it outhere.
(defpackage :demoserv2
(:use :hunchentoot
:cl-who
:cl)
(:export
:start-server))
(in-package :demoserv2)
(setq *dispatch-table* (list (create-regex-dispatcher "^/login$" 'login)))
(defparameter *users* '(("mags" "icecream" (:scary-hair :evil))
("annie" "mrpugwash" (:ann-widdicombe))
("tony" "gwbush" (:evil :boring :scary-wife))
("john" "grey" (:boring))))
(defvar *our-mutex* (hunchentoot-mp:make-lock "our-lock"))
(defvar *events* '())
; Add event to log
(defun add-event (user text)
(hunchentoot-mp:with-lock (*our-mutex*)
(push `(,user ,text) *events*)))
; Does this user have access to these roles?
(defun has-access (user-details required-roles)
(reduce #'(lambda (a b) (and a b))
(mapcar #'(lambda (role) (find role (second user-details)))
required-roles)))
; Get user details from session
(defmacro with-user-details (user-details &body body)
`(let ((,user-details (session-value :user)))
,@body))
; Display body if the user is allowed access it
(defmacro display-with-roles (user-details required-roles &body body)
`(if (and user-details (has-access ,user-details ,required-roles))
,@body))
; Template
(defmacro with-template (title user-details &body body)
`(with-html-output-to-string (*standard-output*)
(:html
(:head (:title (fmt "Hunchentoot demo - ~a" ,title)))
(:body (:h1 "Hunchentoot Demo")
(:div (:a :href "/" "Menu") " - "
(:a :href "/events" "Events")
(if user-details
(htm " - " (:a :href "/logout" "Logout"))))
,@body))))
; Defines a normal page with basic infrastructure
(defmacro defpage (name url required-roles user-details &body body)
`(progn
(defun ,name ()
(with-user-details ,user-details
(if (or (not ,required-roles)
(has-access ,user-details ,required-roles))
(progn ,@body)
"You're not allowed view this page")))
(push (create-regex-dispatcher ,(format nil "^/~a$" url) ',name)
*dispatch-table*)))
; Defines an event page
(defmacro defevent (name url required-roles text)
`(defpage ,name ,url ,required-roles user-details
(add-event (first user-details) ,text)
(with-template ,text user-details
(htm (:h3 "Event Registered") (str ,text)))))
; Index
(defpage index-page "" nil user-details
(with-template "Index" user-details
(:div
(if (not user-details)
(htm (:form :action "/login" :method "post"
"Username:" (:input :type "text" :name "username")
"Password:" (:input :type "password" :name "password")
(:input :type "submit" :value "submit"))))
(htm (:ul
(display-with-roles user-details '(:scary-hair)
(htm (:li (:a :href "/impose-poll-tax" "Impose Poll Tax"))))
(display-with-roles user-details '(:evil)
(htm (:li (:a :href "/have-a-war" "Have a Nice War"))))
(display-with-roles user-details '(:scary-hair :evil)
(htm (:li (:a :href "/ice-cream" "Invent Soft Ice-cream"))))
(display-with-roles user-details '(:boring)
(htm (:li (:a :href "/eat-peas" "Eat Peas"))))
(display-with-roles user-details '(:scary-wife)
(htm (:li (:a :href "/flats" "Have Flat Investment Scandal"))))
(display-with-roles user-details '(:ann-widdicombe)
(htm (:li (:a :href "/celeb-fat" "Go on Celebrity Fat Farm")))))))))
; Events
(defevent impose-poll-tax "impose-poll-tax" '(:scary-hair) "Poll tax imposed!")
(defevent celeb-fat "celeb-fat" '(:ann-widdicombe) "Went to Celebrity Fat Farm!")
(defevent have-a-war "have-a-war" '(:evil) "Had a nice war. I do like those.")
(defevent ice-cream "ice-cream" '(:evil :scary-hair) "Invented soft ice-cream.")
(defevent eat-peas "eat-peas" '(:boring) "Peas are nice, dear!")
(defevent flats "flats" '(:scary-wife) "Buy flat through fraudster.")
(defpage logout "logout" nil user-details
(delete-session-value :user)
(redirect "/"))
; Events listing
(defpage events "events" nil user-details
(let* ((user (parameter "user"))
(our-events (if user
(remove-if-not #'(lambda (a) (equal user a))
(hunchentoot-mp:with-lock (*our-mutex*)
*events*)
:key #'first)
(hunchentoot-mp:with-lock (*our-mutex*)
*events*))))
(with-template "Events" user-details
(htm (:h3 "Events")
(:ul
(dolist (i our-events)
(htm (:li (:a :href (format nil "/events?user=~a" (first i)) (str (first i))) " - "
(str (second i))))))))))
(defun login ()
(let ((username (parameter "username"))
(password (parameter "password")))
(let ((user-details (find username *users* :test #'equal :key #'first)))
(if user-details
(cond ((equal (second user-details) password)
(start-session)
(setf (session-value :user) (list (first user-details)
(third user-details)))
(redirect "/"))
(t "Bad password"))
"Non-existent user"))))
Now, before I start, I should note that the sort of thing I'm going to be doing is very much personal taste, and may not suit your development style at all. I'm not even sure that it suits mine that well, but it's just a little introduction to give you a feel for the sorts of things that you can do easily in Lisp, and how they relate to writing a web application.
I'm going to be writing a simple, pointless webapp with user login. For this, I will put together a very simple framework. Frameworks are, of course, very popular in the web development world, and tend, by and large, to be vast, constraining things suited for writing a particular type of application. I hope to show here that it is easy, in Lisp, to put together a simple framework to suit your application's needs as you write it.
I'm going to be using macros here. Macros in Common Lisp are quite sophisticated creatures; they are effectively functions which produce code at runtime. I'll also be using a couple of nice Hunchentoot features that I didn't mention the first time around; sessions, which let you store data associated with a web client, and hunchentoot-mp, a set of cross-platform functions for dealing with multi-processing which comes with Hunchentoot. I'll also be using cl-who, a HTML generation library.
Hunchentoot sessions work by giving the client a session-identifying cookie, and storing the session data itself in memory on the webserver. This approach has its limitations; in particular, it is problematic if you are using more than one webserver; however, it is fine for a simple application like this. The multi-processing functions are handy because the Hunchentoot server runs as a series of Lisp processes (threads) in a single Lisp environment; if we need to store non-user-specific data in memory, we have to prevent two users attempting to write to it at the same time.
The web application consists of an authentication-protected site where users can perform a variety of actions depending on their assigned roles. Actions will set statuses, much like on Facebook; they'll be stored in a mutex-locked list. Users will be able to view just their own statuses, or everyone's. Each user can have more than one role, and each role can require one or more roles. As it's just a little example, there'll be no persistence; user accounts will be defined in a list, and statuses will be stored in memory. In a real application of this type, of course, you'd probably use a database or other persistent store.
It would be convenient to have details on the current user from the session available in each page handler. One way to handle this would be to just do a (let ((user-details ...)))enclosing the handler contents, but the way I'm going to do it is writing a macro with syntax (with-user-details (user-details) ...). There's not much obvious gain from this in this instance, but it looks a bit nicer, and it would be convenient if more work ever had to be done in that session data retrieval bit. For instance, you might want to add a line to a log every time a logged-in user viewed a page, or do something with the result of the page generation function.
I'd also like to be able to have certain parts of the page only displayed for registered users with the correct roles. For this, I'll use another little macro. (display-with-roles user-details list-of-roles ...)
I'd also like to use a basic page template. Again, this can be nicely implemented in a macro, and then the display code for a given page wrapped in (with-template ...).
Finally, it looks like there's a fair bit of functionality every page is going to require. Page handlers are functions, but there's no reason that we have to define them using plain old defun. Instead, we have yet another macro, defpage. This macro wraps the code provided to it in the with-user-details macro, and the with-template macro. It also puts in code to check that the user is allowed to view the page at all, and adds the page handler to hunchentoot's dispatch table. (defpage page-name url roles-required ..).
I'll also have a (defevent page-name url roles-required text). This will just create a page which performs an event.
So, without further ado, here's the code. It's not perfect, and for this small application it's possibly a bit long, but it does make it easier to write apps of this sort. You can try it outhere.
(defpackage :demoserv2
(:use :hunchentoot
:cl-who
:cl)
(:export
:start-server))
(in-package :demoserv2)
(setq *dispatch-table* (list (create-regex-dispatcher "^/login$" 'login)))
(defparameter *users* '(("mags" "icecream" (:scary-hair :evil))
("annie" "mrpugwash" (:ann-widdicombe))
("tony" "gwbush" (:evil :boring :scary-wife))
("john" "grey" (:boring))))
(defvar *our-mutex* (hunchentoot-mp:make-lock "our-lock"))
(defvar *events* '())
; Add event to log
(defun add-event (user text)
(hunchentoot-mp:with-lock (*our-mutex*)
(push `(,user ,text) *events*)))
; Does this user have access to these roles?
(defun has-access (user-details required-roles)
(reduce #'(lambda (a b) (and a b))
(mapcar #'(lambda (role) (find role (second user-details)))
required-roles)))
; Get user details from session
(defmacro with-user-details (user-details &body body)
`(let ((,user-details (session-value :user)))
,@body))
; Display body if the user is allowed access it
(defmacro display-with-roles (user-details required-roles &body body)
`(if (and user-details (has-access ,user-details ,required-roles))
,@body))
; Template
(defmacro with-template (title user-details &body body)
`(with-html-output-to-string (*standard-output*)
(:html
(:head (:title (fmt "Hunchentoot demo - ~a" ,title)))
(:body (:h1 "Hunchentoot Demo")
(:div (:a :href "/" "Menu") " - "
(:a :href "/events" "Events")
(if user-details
(htm " - " (:a :href "/logout" "Logout"))))
,@body))))
; Defines a normal page with basic infrastructure
(defmacro defpage (name url required-roles user-details &body body)
`(progn
(defun ,name ()
(with-user-details ,user-details
(if (or (not ,required-roles)
(has-access ,user-details ,required-roles))
(progn ,@body)
"You're not allowed view this page")))
(push (create-regex-dispatcher ,(format nil "^/~a$" url) ',name)
*dispatch-table*)))
; Defines an event page
(defmacro defevent (name url required-roles text)
`(defpage ,name ,url ,required-roles user-details
(add-event (first user-details) ,text)
(with-template ,text user-details
(htm (:h3 "Event Registered") (str ,text)))))
; Index
(defpage index-page "" nil user-details
(with-template "Index" user-details
(:div
(if (not user-details)
(htm (:form :action "/login" :method "post"
"Username:" (:input :type "text" :name "username")
"Password:" (:input :type "password" :name "password")
(:input :type "submit" :value "submit"))))
(htm (:ul
(display-with-roles user-details '(:scary-hair)
(htm (:li (:a :href "/impose-poll-tax" "Impose Poll Tax"))))
(display-with-roles user-details '(:evil)
(htm (:li (:a :href "/have-a-war" "Have a Nice War"))))
(display-with-roles user-details '(:scary-hair :evil)
(htm (:li (:a :href "/ice-cream" "Invent Soft Ice-cream"))))
(display-with-roles user-details '(:boring)
(htm (:li (:a :href "/eat-peas" "Eat Peas"))))
(display-with-roles user-details '(:scary-wife)
(htm (:li (:a :href "/flats" "Have Flat Investment Scandal"))))
(display-with-roles user-details '(:ann-widdicombe)
(htm (:li (:a :href "/celeb-fat" "Go on Celebrity Fat Farm")))))))))
; Events
(defevent impose-poll-tax "impose-poll-tax" '(:scary-hair) "Poll tax imposed!")
(defevent celeb-fat "celeb-fat" '(:ann-widdicombe) "Went to Celebrity Fat Farm!")
(defevent have-a-war "have-a-war" '(:evil) "Had a nice war. I do like those.")
(defevent ice-cream "ice-cream" '(:evil :scary-hair) "Invented soft ice-cream.")
(defevent eat-peas "eat-peas" '(:boring) "Peas are nice, dear!")
(defevent flats "flats" '(:scary-wife) "Buy flat through fraudster.")
(defpage logout "logout" nil user-details
(delete-session-value :user)
(redirect "/"))
; Events listing
(defpage events "events" nil user-details
(let* ((user (parameter "user"))
(our-events (if user
(remove-if-not #'(lambda (a) (equal user a))
(hunchentoot-mp:with-lock (*our-mutex*)
*events*)
:key #'first)
(hunchentoot-mp:with-lock (*our-mutex*)
*events*))))
(with-template "Events" user-details
(htm (:h3 "Events")
(:ul
(dolist (i our-events)
(htm (:li (:a :href (format nil "/events?user=~a" (first i)) (str (first i))) " - "
(str (second i))))))))))
(defun login ()
(let ((username (parameter "username"))
(password (parameter "password")))
(let ((user-details (find username *users* :test #'equal :key #'first)))
(if user-details
(cond ((equal (second user-details) password)
(start-session)
(setf (session-value :user) (list (first user-details)
(third user-details)))
(redirect "/"))
(t "Bad password"))
"Non-existent user"))))
Subscribe to:
Posts (Atom)