<?xml version='1.0' encoding='utf-8' ?>
<!--  If you are running a bot please visit this policy page outlining rules you must respect. http://www.livejournal.com/bots/  -->
<rss version='2.0' xmlns:lj='http://www.livejournal.org/rss/lj/1.0/' xmlns:media='http://search.yahoo.com/mrss/' xmlns:atom10='http://www.w3.org/2005/Atom'>
<channel>
  <title>Informal Methods</title>
  <link>http://ahefner.livejournal.com/</link>
  <description>Informal Methods - LiveJournal.com</description>
  <lastBuildDate>Mon, 23 Jan 2012 12:15:37 GMT</lastBuildDate>
  <generator>LiveJournal / LiveJournal.com</generator>
  <lj:journal>ahefner</lj:journal>
  <lj:journalid>14248520</lj:journalid>
  <lj:journaltype>personal</lj:journaltype>
  <image>
    <url>http://l-userpic.livejournal.com/71615828/14248520</url>
    <title>Informal Methods</title>
    <link>http://ahefner.livejournal.com/</link>
    <width>100</width>
    <height>100</height>
  </image>

<item>
  <guid isPermaLink='true'>http://ahefner.livejournal.com/19770.html</guid>
  <pubDate>Mon, 23 Jan 2012 12:15:37 GMT</pubDate>
  <title>Piddling Plugins</title>
  <link>http://ahefner.livejournal.com/19770.html</link>
  <description>&lt;p&gt;The &lt;a href=&quot;https://github.com/ahefner/shuffletron&quot; rel=&quot;nofollow&quot;&gt;Shuffletron&lt;/a&gt; music player, in various branches, has accumulated some neat features (particularly &lt;a href=&quot;http://last.fm&quot; rel=&quot;nofollow&quot;&gt;last.fm&lt;/a&gt; scrobbling in &lt;a href=&quot;https://github.com/redline6561/shuffletron&quot; rel=&quot;nofollow&quot;&gt;Brit Butler&apos;s&lt;/a&gt; branch) that deserve merging, and ought to be cleanly separated from the core of the program. Leslie Polzer sent me a novel implementation early on which used generic functions for extensibility, adding/removing methods via the MOP as plugins load/unload. Clever as that was (and I&apos;m impressed how little code is required, rereading the patch now), I wasn&apos;t comfortable with it, and the lack of a pressing need for a plugin interface let me put it off for a good long while.&lt;/p&gt;

&lt;p&gt;Building extensibility around generic functions seemed the right thing to do though, and a slightly different idea, of writing plugins in the style of mixins and calling CHANGE-CLASS to enable them at runtime, stuck in the back of my head until (with some prodding) I was motivated to try it out. It&apos;s hardly a new idea (both Gsharp and McCLIM contain implementations of similar ideas, as does the AMOP book, just to name a few examples), and a minimal implementation doesn&apos;t take much code at all:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;(defvar *configurations* (make-hash-table :test &apos;equal))

(defun configuration (plugins)
  (or (gethash plugins *configurations*)
      (setf (gethash plugins *configurations*)
            (make-instance &apos;standard-class
                           :name (format nil &quot;MY-APPLICATION~{/~A~}&quot; plugins)
                           :direct-superclasses (cons (find-class &apos;my-application)
                                                      (mapcar #&apos;find-class plugins))))))

(defun reconfigure (application plugins &amp;rest initargs)
  (apply #&apos;change-class application (configuration plugins) initargs))

(defun active-plugins (instance)
  (mapcar #&apos;class-name (rest (sb-mop:class-direct-superclasses (class-of instance)))))

(defun enable-plugin (application plugin &amp;rest initargs)
  (apply #&apos;reconfigure application (adjoin plugin (active-plugins application)) initargs))

(defun disable-plugin (application plugin)
  (reconfigure application (remove plugin (active-plugins application))))

(defun make-application (&amp;rest initargs)
  (apply &apos;make-instance (configuration &apos;()) initargs))&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This isn&apos;t an ideal implementation, and there&apos;s a limit to how good it&apos;s going to get when CLOS doesn&apos;t fully support anonymous classes. However, a more serious attempt should work on arbitrary classes, provide a place to hang init/shutdown code for plugins, and the ability to list which plugins are enabled within an instance.&lt;/p&gt;

&lt;h2&gt;Piddling Plugins&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/ahefner/piddling-plugins&quot; rel=&quot;nofollow&quot;&gt;Piddling-plugins&lt;/a&gt; adds these features using only slightly more code than above, along with some superfluous macro magic for writing defun-style definitions that are extensible by plugins. I&apos;ve made light use of it in a branch of Shuffletron, confirming to myself that it&apos;s a good fit.&lt;/p&gt;
&lt;p&gt;The code is tiny and self-explanatory, so I&apos;ll just post the examples from the README file for fun.&lt;/p&gt;

&lt;h2&gt;Examples&lt;/h2&gt;

&lt;p&gt;Imagine we have written a music player, looking something like this deliberately simplified code:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;(defclass music-player () ())

(defun run-music-player ()
  ;; You need to set or bind *application* to your application instance
  ;; if you use defun-extensible. It&apos;s a good idea even if you don&apos;t.
  (let ((*application* (make-instance &apos;music-player)))
    (init-audio)
    (init-library *application*)
    (loop (execute-command (read-line)))))
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Functions extensible by plugins can be defined using &lt;code&gt;DEFUN-EXTENSIBLE&lt;/code&gt;.
This is just syntactic sugar for defining a generic function specialized
on the application object, with a wrapper that passes in the value of &lt;code&gt;*APPLICATION*&lt;/code&gt;.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;(defun-extensible execute-command (command)
  ...)

(defun-extensible play-song (song)
  ...)

(defun-extensible song-finished (song)
  ...)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Plugins extend the behavior of the application by defining methods on the extensible functions (or rather the generic functions defined behind the scenes, which are prefixed by &quot;EXTENDING-&quot;):&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;(defclass scrobbler ()
  ((auth-token :accessor auth-token)))

(defmethod plugin-enabled (app (plugin-name (eql &apos;scrobbler)) &amp;amp;key &amp;amp;allow-other-keys)
  (setf (auth-token app) (get-auth-token))
  (format t &quot;~&amp;amp;Scrobbler enabled.~%&quot;))

(defmethod plugin-disabled (app (plugin-name (eql &apos;scrobbler)))
  (format t &quot;~&amp;amp;Scrobbler disabled.~%&quot;))

(defmethod extending-song-finished :after ((plugin scrobbler) song)
  (scrobble song (auth-token plugin)))

(defclass status-bar () ())

(defmethod extending-play-song :after ((plugin status-bar) song)
  (redraw-status-bar))

(defmethod extending-execute-command :after ((plugin status-bar) command-line)
  (declare (ignore command-line))
  (redraw-status-bar))
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;To enable a plugin:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;(enable-plugin *application* NAME [INITARGS...])
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;To disable a plugin:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;(disable-plugin *application* NAME)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;To set precisely which plugins are enabled:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;(reconfigure *application* LIST-OF-PLUGINS [INITARGS...])
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;References&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Strandh R., Hamer J., Baumann G. &quot;Using Stealth Mixins to Achieve Modularity&quot; (2007)
&lt;ul&gt;
&lt;li&gt;Implentation in Gsharp: &lt;a href=&quot;http://common-lisp.net/cgi-bin/gitweb.cgi?p=projects/gsharp/gsharp.git;a=blob;f=utilities.lisp&quot; rel=&quot;nofollow&quot;&gt;http://common-lisp.net/cgi-bin/gitweb.cgi?p=projects/gsharp/gsharp.git;a=blob;f=utilities.lisp&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&quot;Modes&quot; implementation in McCLIM&apos;s ESA framework (formerly part of Climacs)
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://git.boinkor.net/gitweb/mcclim.git/blob/HEAD:/ESA/utils.lisp#l446&quot; rel=&quot;nofollow&quot;&gt;http://git.boinkor.net/gitweb/mcclim.git/blob/HEAD:/ESA/utils.lisp#l446&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Throwaway classes (comp.lang.lisp): &lt;a href=&quot;http://groups.google.com/group/comp.lang.lisp/browse_thread/thread/78ef4a5fcd6a1661?pli=1&quot; rel=&quot;nofollow&quot;&gt;http://groups.google.com/group/comp.lang.lisp/browse_thread/thread/78ef4a5fcd6a1661?pli=1&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;The Art of the Metaobject Protocol, Section 2.4
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://www.foldr.org/~michaelw/lisp/amop-programmatic-class.lisp&quot; rel=&quot;nofollow&quot;&gt;http://www.foldr.org/~michaelw/lisp/amop-programmatic-class.lisp&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;ContextL - &lt;a href=&quot;http://common-lisp.net/project/closer/contextl.html&quot; rel=&quot;nofollow&quot;&gt;http://common-lisp.net/project/closer/contextl.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Dynamic Classes - &lt;a href=&quot;http://common-lisp.net/project/dynamic-classes/&quot; rel=&quot;nofollow&quot;&gt;http://common-lisp.net/project/dynamic-classes/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
  <comments>http://ahefner.livejournal.com/19770.html</comments>
  <category>lisp</category>
  <category>shuffletron</category>
  <category>hacks</category>
  <category>coding</category>
  <lj:security>public</lj:security>
  <lj:reply-count>3</lj:reply-count>
</item>
<item>
  <guid isPermaLink='true'>http://ahefner.livejournal.com/16875.html</guid>
  <pubDate>Sat, 12 Dec 2009 19:15:29 GMT</pubDate>
  <title>Shuffletron bug: dirent.dt_type</title>
  <link>http://ahefner.livejournal.com/16875.html</link>
  <description>&lt;a href=&quot;http://vintage-digital.com/hefner/software/shuffletron/&quot; rel=&quot;nofollow&quot;&gt;Shuffletron&lt;/a&gt;, despite its toyish demeanor, makes an effort to do certain things correctly and efficiently which are critical to its being useful as a music player. To that end, it bypasses the &lt;a href=&quot;http://fare.livejournal.com/139755.html&quot; rel=&quot;nofollow&quot;&gt;hopeless&lt;/a&gt; Common Lisp &lt;a href=&quot;http://www.lispworks.com/documentation/lw50/CLHS/Body/19_ab.htm&quot; rel=&quot;nofollow&quot;&gt;pathname&lt;/a&gt; system in favor of using strings and POSIX system calls, and sidesteps the increasingly prevalent and often misguided &amp;quot;UTF-8 or bust!&amp;quot; mentality in favor of a more robust Unix-like approach of being encoding-agnostic by passing 8-bit characters transparently.&lt;br /&gt;&lt;br /&gt;In particular, I made a point of ensuring that the library scan (a recursive walk of the directory) completes as quickly as possible, within reason. My benchmark was the combination of find and grep, which not only traverses the entire tree but does some useful processing on the result. It&apos;s not possible to reach that speed without writing excessively low-level Lisp code, but I got close enough that I was happy with the result. It may sound surprising for a task like directory traversal, but any cute processing your Lisp&apos;s filesystem interface does - consing/converting strings, decoding characters, building pathnames, converting C structures into nicer Lisp structures, etc. - quickly eats away at this peak performance. Those system calls run a lot faster than I first expected, once the relevant kernel caches are warmed up.&lt;br /&gt;&lt;br /&gt;I&apos;ve been playing with &lt;a href=&quot;http://fuse.sourceforge.net/sshfs.html&quot; rel=&quot;nofollow&quot;&gt;sshfs&lt;/a&gt; this evening and wanted to try using it to access my music library on the other side of the planet from a local Shuffletron instance (with appalling but sufficient latency and bandwidth, despite ostensibly being multi-megabit connections on both sides). Oddly, Shuffletron couldn&apos;t find any files - it said the library was empty. I soon recalled a potential corner I had cut in the filesystem scanning code.&lt;br /&gt;&lt;br /&gt;While scanning the directory tree, you have to figure out whether each entry is a file, directory, or something else. Originally I had done this by calling stat and checking the st_mode field (I&apos;d borrowed the functions from some code that had other reasons to stat the file). While converting the code from sb-posix to &lt;a href=&quot;http://common-lisp.net/project/osicat/&quot; rel=&quot;nofollow&quot;&gt;osicat-posix&lt;/a&gt; for portability to &lt;a href=&quot;http://trac.clozure.com/ccl&quot; rel=&quot;nofollow&quot;&gt;CCL&lt;/a&gt; (and beyond!), I cleaned it up to use the struct dirent.d_type field instead, obviating the call to stat. man 2 stat says this about the d_type field:&lt;br /&gt;&lt;br /&gt;&lt;blockquote&gt;Other than Linux, the d_type field is available mainly only on BSD systems.  This field makes it possible to avoid  the  expense  of  calling stat(2)  if  further  actions  depend  on the type of the file.  If the _BSD_SOURCE feature test macro is defined, then glibc defines the  following macro constants for the value returned in d_type:&lt;br /&gt;&lt;/blockquote&gt;&lt;br /&gt;It&apos;s available on Linux (except for old versions)? Great, my program only works on Linux anyway, I&apos;ll use it! So went my reasoning. I had some misgivings on account of  the note &amp;quot;If  the  file  type  could  not  be determined, the value DT_UNKNOWN is returned in d_type,&amp;quot; but it seemed to work well enough, and figured I&apos;d just move on rather than write the extra eight lines (!) of code to deal with what appeared to be an obscure case. &lt;br /&gt;&lt;br /&gt;With sshfs, I finally found that obscure case. The dtype is always set to DT_UNKNOWN, and shuffletron can&apos;t get past the root directory of the library. Despite having written a (&lt;a href=&quot;http://ahefner.livejournal.com/14789.html&quot; rel=&quot;nofollow&quot;&gt;simple&lt;/a&gt;) FUSE filesystem myself, it isn&apos;t immediately clear how you can make that work from the filesystem&apos;s side, but it is clear that it was time to add a fallback to calling stat. Having done so, Shuffletron now happily scans my music library over sshfs, which would be great, except that it also takes something like 15 minutes walk the directory tree (even using find!), because every directory appears to involve several trips over the internet. Hundreds of directories times a few seconds each.. not good (this same thing annoys me when copying multiple small files via SCP, incidentally). Just goes to show that presenting something (a computer many thousands of miles away)  as something it&apos;s not (a local filesystem) can only work so well. If only there were an interface (is there an interface?) to indicate the desired access pattern, so that sshfs could pull all that information quickly in a single batch and keep it in its cache.&lt;br /&gt;&lt;br /&gt;This little fix will be present in the forthcoming Shuffletron 0.0.5 release, along with other great features that I&apos;ve been dutifully testing since July or so. :)</description>
  <comments>http://ahefner.livejournal.com/16875.html</comments>
  <category>shuffletron</category>
  <category>coding</category>
  <category>linux</category>
  <lj:security>public</lj:security>
  <lj:reply-count>6</lj:reply-count>
</item>
</channel>
</rss>
