Content AND Presentation

(Note to English-speaking readers: the links entitled Komentarze na tej stronie lead to comment pages.)

2015-08-22 Beeminder.el

Last year, I started using a very interesting service. While I’m usually quite skeptical towards various motivational tools (for instance, I once tried org-habits, and it turned out that it didn’t work at all – just seeing the continuous string of asterisks seems to be not motivating enough for me…), I have to admit that I found this one to be surprisingly effective.

The service is called Beeminder, and has a few rather unique features.

First of all, its main application is for motivating oneself to regularly do anything measurable. (Note the key words: regularly and measurable.) You sign up for an account (which itself is free of charge, at least the basic version), enter a so-called “goal” (like: I want to write one blog post per week), which includes a weekly (or daily, or hourly etc.) rate – here, one per week. Then you can see a line whose gradient is the defined rate, each time you write a blog post you enter a datapoint, all your datapoints are joined to make a (hopefully) nice graph, and if your graph ever falls below the said line, you lose, or derail (they have quite a specific jargon there, which works nice, however).

And now the fun begins. The first time you derail, nothing really bad happens – it’s just that your graph doesn’t look that nice anymore, and you have to enter your credit card number. The second time you derail, they charge you five bucks. The third time it’s ten bucks, and then it proceeds geometrically (with the ratio of 3). Yes, it’s that brutal. Yes, it works.

Of course, there must be the way to e.g. change your rate, or even cancel a goal etc. On the other hand, if you could just do that, there would be no point in the system altogether. Here’s the catch: you can freely cancel your goals, or change their rate etc., however, any such change is effective a week from now.

Brilliant, eh? (Note: the above description is a bit simplified, but you get the idea.)

Of course, the system works on a honorary basis: nothing can stop you from entering comletely fake data about what you have done. There is virtually no way they could avoid that, but – as the FAQ states – if you are a kind of person who would enter such fake data, you wouldn’t even get so far in the FAQ.

(As a side note, I have to say that I like systems based on honesty. They are of course quite rare, but not totally uncommon. Once I ordered something on the internet, and after a few days I received my order together with the payment instructions. Though probably the fact that it was a copy of Bible did play a role;-).)

Now while Beeminder is really nice – and I can attest that it’s not a fad in my case, since I’ve been using it for more than one year now on a daily basis – it does have its drawbacks. There are three basic ways you can enter your data. One is the website, which thoroughly sucks. This is not a huge problem, though, since I use my mobile phone 99.9% of the time anyway. I don’t know about iOS – I don’t touch Mac-anything even with a ten-foot pole – but the Android app is really good.

Sometimes, however, using your phone is not the most convenient method. For instance, I have a goal of walking at least 4 kilometers per day, and I use a GPS tracking app to gather data about my hikes. Since a machine already has my data, it would be a nuisance to copy it manually to Beeminder. Enter auto-updated goals: there is integration with quite a lot of services, and all I have to do is to start and stop my tracking app – Beeminder scraps its database once a day, and that’s it.

And now comes the coolest part. It is possible to add your own service/application to the ecosystem! The authors of Beeminder exposed a decent API, and all you have to do is send some HTTP requests and you can have your datapoints updated, or info about goals downloaded (as JSON).

So now, after this very long introduction, you can guess where I’m going. Yes, I wrote an Emacs client for Beeminder. Currently, you can only do the most basic things: display your goals (sorted by the time left to derailment) and update a goal (i.e., create a datapoint). (BTW, some time ago I wrote about EWOC; it is this project I needed it for.)

What is probably the most important (at least for me), is that my client supports two things which are not supported by the official one. One of them is sorting: the official client cannot change the sorting criterion. In my one, you can currently sort according to two criteria (both are useful, at least for me).

The other one is filtering. I can hide goals for which I’ve already done enough today, and I can hide goals whose derailment time is far in the future. (Both “enough for today” and “far from the future” are of course customizable through prefix arguments, with default values customizable through user options. This is Emacs, after all.) I can also hide individual goals (for instance, if for some reason I’m not going to work in them today anyway), so that I’m not distracted by them.

I plan to work at least on two more areas. One is making the client more sophisticated, with possibilities of e.g. modifying datapoints or changing the rate. (This is not crucial, however, since such activities are much less common than the two main ones: displaying what has to be done and marking it done.) Another one, which is quite important for me, is Org-mode integration. It will be possible to mark a TODO item as either something that must be done given number of times (e.g., once every day, or once every other day, etc.), so that each time the item is marked DONE (of course, for this to make sense, it probably should be one with a repeater), a datapoint of one is send to Beeminder. For instance – to take an example from the Org manual itself – if I wanted to shave (on average) at least three times a week, I would be able set a Beeminder goal with a rate of 3 times weekly, and make an Org habit just like in the Org manual – then mark this habit (using properties) so that each time it is marked DONE, Emacs sends a HTTP request using the Beeminder API so that a datapoint of one is uploaded. Another possibility is a goal of “x minutes per day” (or “x hours per day”) – in fact, most of may goal are of this type. Of course, it will be integrated with Org-mode’s clocking feature.

So please stay tuned. The client is still a bit simplistic as of now, but is already usable – in fact, I’ve been using it for the past few weeks – and is available here. Bug reports/feature requests are welcome.

CategoryEnglish, CategoryBlog, CategoryEmacs, CategoryOrgMode

Komentarze na tej stronie

2015-08-08 Concatenation with indentation

Quite recently, I was coding two (independent) Emacs projects involving generating XML files. I found the xmlgen Emacs library, learned to use it (well, it’s trivial: basically, it defines a mapping from the list structure to XML, and lets specify both tags and attributes in a natural way), and dropped it. The reason was that I wanted my XML files to be human-readable, and xmlgen did not pretty-print them at all. (There is a pretty-printer of XML (in fact, SGML) files in Emacs, but I didn’t like it: it sprinkled my file with newlines in places I didn’t want them.)

Since the files I’m generating are quite simple, I decided to use format a few times and call it a day. Well, simple or not, it was a bit awkward, especially with indentation. So I thought that I could write a small function, which – given a list of strings and the amount of indentation – would format them accordingly. And so here it is, just in case someone needs it.

(defun concat-lines-with-indentation (indent &rest lines)
  "Given the amount of indentation and some strings (LINES),
concatenate them, prepending INDENT spaces to each line."
  (apply #'concat (let ((indent-string (make-string indent 32)))
                    (mapcar (lambda (line)
                              (concat indent-string
                                      line
                                      (unless (eq (elt line (1- (length line))) ?\n)
                                        "\n")))
                            lines))))

There aren’t very many terribly interesting things about this code – it’s fairly standard, but it turned out to be helpful (at least for me). (What I miss in the above code – because of my use-case – is the ability to mix strings and list of strings in the lines argument, but I was just a bit too lazy to code that.)

CategoryEnglish, CategoryBlog, CategoryEmacs

Komentarze na tej stronie

2015-08-01 case-intern trick

When coding in Elisp, sometimes it happens that you want to branch depending on the value of some variable, which can hold a few possible values. The C way would be to use an enum; the lispy way is to use symbols and case. However, sometimes you have strings and not symbols. If you can arrange things so that you have symbols, it’s fine; sometimes you can’t, though, especially if you use someone else’s code. For instance, when writing an Org-mode exporter, you might want to dispatch on link types; however, (org-element-property :type link) is a string (one of a few enumerated in the org-link-types variable).

Of course, you can just use cond:

(cond
 ((string= (org-element-property :type link) "http") (do-something))
 ((string= (org-element-property :type link) "file") (do-something-else))
 (t (do-something-else-yet)))

This is far from elegant, though.

Here is one possible solution. While Elisp doesn’t have a case form, cl (the package implementing many Common Lisp features) has cl-case (conveniently aliased to case). However, it uses eql to compare keys. Since (eql "foo" "foo") evaluates to nil (because these two strings are different objects in memory), case‘ing on strings doesn’t make sense.

The solution is to use intern. Basically, it may be considered as a “type cast from strings to symbols”. (This is just a bit more complicated, but never mind.) For instance, (eql (intern "foo") (intern "foo")) evaluates to t, as expected. This means that the above construct may be rewritten much more elegantly as

(case (intern (org-element-property :type link))
  (http (do-something))
  (file (do-something-else))
  (t (do-something-else-yet)))

Worth remembering.

CategoryEnglish, CategoryBlog, CategoryEmacs, CategoryOrgMode

Komentarze na tej stronie

Więcej...

(Więcej means More in Polish; click it to see older entries.)

CategoryEnglish, CategoryBlog