<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet href="/stylesheets/rss.css" type="text/css"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/">
  <channel>
    <title>Tom Moertel's Weblog: Category pittsburgh</title>
    <link>http://blog.moertel.com/articles/category/pittsburgh</link>
    <language>en-us</language>
    <ttl>40</ttl>
    <description>Quality rants on programming theory and stuff geeks like</description>
    <item>
      <title>The 2006 Pittsburgh Perl Workshop is a ten-ton can of programming whoop-ass</title>
      <description>&lt;p&gt;I am on the planning committee for the &lt;a href="//pghpw.org/"&gt;2006 Pittsburgh Perl
Workshop&lt;/a&gt; and serve as the primary point of contact for
speakers.  That means I get the inside scoop on the talks.
And from what I have seen, all I can say is, &lt;em&gt;These
talks kick ass.&lt;/em&gt;  Well, actually I can say one more thing:&lt;/p&gt;


&lt;p style="padding-left:2em; padding-right:2em"&gt;&lt;em&gt;
If there is any possible way you can manage to get yourself to Pittsburgh, Pennsylvania
on Saturday, 23 September 2006, &lt;strong&gt;do not wait, do not mull
it over, &lt;a href="http://pghpw.org/register.html"&gt;register now for the Pittsburgh Perl Workshop&lt;/a&gt;&lt;/strong&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;If you miss this one, you&amp;#8217;ll probably end up weeping in front of your keyboard for a long, long time.&lt;/p&gt;

	&lt;p&gt;&lt;a href="http://pghpw.org/"&gt;&lt;img src="http://community.moertel.com/~thor/pix/20060912/perl-at-work-sign.png" style="float: right; margin-left: 2em;;" title="Perl At Work" alt="Perl At Work" /&gt;&lt;/a&gt;&lt;/p&gt;


	&lt;p&gt;&lt;span class="caps"&gt;PPW&lt;/span&gt;&amp;#160;&amp;#8217;06 has speakers from the worlds of finance,
bioinformatics, engineering, politics, health care, insurance, Web-2.0
start-ups, environmental monitoring, and mathematics.  And they all
have fascinating things to share with you about how they make Perl
work for them and about how you can make Perl work for you.&lt;/p&gt;


	&lt;p&gt;Registration is only $20 &amp;#8211; can you imagine that, in an age when
programming conferences routinely cost &lt;a href="http://weblog.rubyonrails.org/2005/11/27/railsconf-2006-june-22-25-chicago"&gt;hundreds&lt;/a&gt; if not &lt;a href="http://java.sun.com/javaone/sf/registration.jsp"&gt;thousands&lt;/a&gt; of dollars? &amp;#8211; and all of the talks show you how to use
Perl to do real work, solve real problems, and make your real life as a
programming professional a whole lot saner. Even if you &lt;em&gt;think&lt;/em&gt;
you don&amp;#8217;t care about Perl, you ought to be a part of
&lt;span class="caps"&gt;PPW&lt;/span&gt;&amp;#160;&amp;#8217;06 just for the rare opportunity to discover something
you may have overlooked.  (At $20, when are you ever going to get
a chance like this again?)&lt;/p&gt;</description>
      <pubDate>Tue, 12 Sep 2006 17:30:00 -0400</pubDate>
      <guid isPermaLink="false">urn:uuid:c9ac1a00-4f9f-46dc-8756-be1963447590</guid>
      <author>Tom Moertel</author>
      <link>http://blog.moertel.com/articles/2006/09/12/the-2006-pittsburgh-perl-workshop-is-a-ten-ton-can-of-programming-whoop-ass</link>
      <category>perl</category>
      <category>pittsburgh</category>
      <category>perl</category>
      <category>pittsburgh</category>
      <category>ppw</category>
      <category>ppw06</category>
      <category>talks</category>
      <category>cool</category>
      <category>stuff</category>
      <trackback:ping>http://blog.moertel.com/articles/trackback/175</trackback:ping>
    </item>
    <item>
      <title>Unofficial answers to FAQs about Giant Eagle's “fuelperks” gas discount</title>
      <description>&lt;p&gt;Local supermarket chain &lt;a href="http://www.gianteagle.com/"&gt;Giant Eagle&lt;/a&gt; has a
brilliant marketing scheme called &amp;#8220;fuelperks&amp;#8221;: For every 50 dollars
you spend on groceries in their stores, you earn a one-time,
10-cent-per-gallon discount at Giant Eagle&amp;#8217;s &amp;#8220;GetGo&amp;#8221; gas stations.
The discounts accumulate until you use them (or they expire in three
months).  If you buy 109 dollars of groceries, for example, you will
earn a 20-cent-per-gallon gas discount.  (The remaining 9 dollars will carry over to your next grocery purchase.)&lt;/p&gt;


	&lt;p&gt;What makes the scheme brilliant is that its psychological effect is
wildly in excess of the discount&amp;#8217;s actual value.  Many people will
wait in lines to buy gas from GetGo, even though the discount
provides no incentive to do so.  Further, as gas prices rise, many
customers perceive the discount to be all the more valuable, even
though it is not.&lt;/p&gt;


	&lt;p&gt;I thought it would be interesting to
scrutinize the discount and answer
some common questions about it.&lt;/p&gt;


	&lt;h3&gt;How much is the &amp;#8220;fuelperks&amp;#8221; discount worth?&lt;/h3&gt;


	&lt;p&gt;The discount is typically equivalent to about 3- to 4-percent cash back on your
grocery purchases, depending on the average amount of gas you purchase
per fill-up.  For example, my mid-size car has a 14-gallon tank, and I always
buy a full tank&amp;#8217;s worth of gas, so for me the discount is about 2.8 percent:&lt;/p&gt;


&lt;p class="example"&gt;
14 gallons &amp;#xD7; 0.10 dollars/gallon / 50 dollars = 0.028
&lt;/p&gt;

	&lt;p&gt;If you drive a luxury-barge &lt;span class="caps"&gt;SUV&lt;/span&gt; and can manage to buy a full 30 gallons at each fill up, you will earn the theoretical maximum discount of 6 percent.&lt;/p&gt;


	&lt;h3&gt;How do I get the maximum benefit from the discount?&lt;/h3&gt;


	&lt;p&gt;To get the maximum benefit, follow two simple rules:&lt;/p&gt;


	&lt;ul&gt;
	&lt;li&gt;buy a full tank of gas whenever you use earned discounts at GetGo&lt;/li&gt;
		&lt;li&gt;use your discounts before they expire&lt;/li&gt;
	&lt;/ul&gt;


	&lt;h3&gt;Do I lose benefits if I buy gas somewhere else?&lt;/h3&gt;


	&lt;p&gt;No.  Even if you buy most of your gas somewhere else, as long as you buy gas from GetGo frequently enough to use your
discounts before they expire, you will get the maximum benefit.&lt;/p&gt;


	&lt;h3&gt; When gas prices increase, do I get more benefits?&lt;/h3&gt;


	&lt;p&gt;No.  The value of any discounts you have earned depend on
the &lt;em&gt;quantity&lt;/em&gt; of gas you purchase.  The &lt;em&gt;price&lt;/em&gt; of gas
at the time of purchase has no effect on the discount.&lt;/p&gt;


	&lt;h3&gt;Any other questions?&lt;/h3&gt;


	&lt;p&gt;If you have any questions (or comments), please post a comment.&lt;/p&gt;</description>
      <pubDate>Wed, 03 May 2006 22:49:00 -0400</pubDate>
      <guid isPermaLink="false">urn:uuid:e5cd7308da84f61c94732ce3660a8274</guid>
      <author>Tom Moertel</author>
      <link>http://blog.moertel.com/articles/2006/05/03/unofficial-answers-to-faqs-about-giant-eagles-fuelperks-gas-discount</link>
      <category>pittsburgh</category>
      <category>marketing</category>
      <trackback:ping>http://blog.moertel.com/articles/trackback/66</trackback:ping>
    </item>
    <item>
      <title>The Supermarket Pricing Kata in Haskell</title>
      <description>&lt;p&gt;At &lt;a href="http://www.insomnia-consulting.org/wiki/index.php/April_27th_Meeting"&gt;last night&amp;#8217;s
meeting&lt;/a&gt;
of the &lt;a href="http://pghcodingdojo.org/"&gt;Pittsburgh Coding Dojo&lt;/a&gt;, we worked
on the &lt;a href="http://blogs.pragprog.com/cgi-bin/pragdave.cgi/Practices/Kata/KataOne.rdoc"&gt;Supermarket Pricing
Kata&lt;/a&gt;.
This particular kata was intended to be food for thought &amp;#8211; a &amp;#8220;shower
kata&amp;#8221; &amp;#8211; but our goal was to do some coding, so we made the problem
more concrete:&lt;/p&gt;


	&lt;ul&gt;
	&lt;li&gt;Come up with a sensible way to represent common supermarket
  pricing rules such as &amp;#8220;buy one, get one free,&amp;#8221; &amp;#8220;three for a dollar,&amp;#8221; 
  &amp;#8221;$0.34 per ounce,&amp;#8221;   and so on&lt;/li&gt;
		&lt;li&gt;Implement a method to check out a shopping cart full of goods,
  applying all applicable pricing rules, and computing the total price
  for the cart&amp;#8217;s contents&lt;/li&gt;
	&lt;/ul&gt;


	&lt;p&gt;Most people paired up, but I worked alone because I wasn&amp;#8217;t ready to code right away.  (Laptop issues.)  At the end of the meeting, nobody had a working solution.  (I guess it
was a shower kata for a reason.) 
I had a partial solution, but I
didn&amp;#8217;t like my internal representation of prices because it conflated
goods and their pricing rules.&lt;/p&gt;


	&lt;p&gt;Over lunch today I came up with a more
sensible representation and finished off my implementation.
Now I&amp;#8217;m happy with it.&lt;/p&gt;


	&lt;h3&gt; The code&lt;/h3&gt;


	&lt;p&gt;Here&amp;#8217;s my solution.  I stripped the comments to emphasize the code
itself (a forest and trees thing).  If you want to see the comments,
see &lt;a href="http://community.moertel.com/~thor/pgh-coding-dojo/SupermarketPricing.hs"&gt;the unstripped source&lt;/a&gt;.&lt;/p&gt;


&lt;div class="typocode"&gt;&lt;pre&gt;&lt;code class="typocode_haskell "&gt;&lt;span class='comment'&gt;{-
   My solution to "The Supermarket Pricing Kata" 
   &lt;a href="http://blogs.pragprog.com/cgi-bin/pragdave.cgi/Practices/Kata/KataOne.rdoc"&gt;http://blogs.pragprog.com/cgi-bin/pragdave.cgi/Practices/Kata/KataOne.rdoc&lt;/a&gt;

   Tom Moertel &amp;lt;tom@moertel.com&amp;gt;
   2006-04-27
-}&lt;/span&gt;

&lt;span class='keyword'&gt;module&lt;/span&gt; &lt;span class='conid'&gt;SupermarketPricing&lt;/span&gt; &lt;span class='keyword'&gt;where&lt;/span&gt;

&lt;span class='keyword'&gt;import&lt;/span&gt; &lt;span class='conid'&gt;Control&lt;/span&gt;&lt;span class='varop'&gt;.&lt;/span&gt;&lt;span class='conid'&gt;Arrow&lt;/span&gt; &lt;span class='layout'&gt;(&lt;/span&gt;&lt;span class='layout'&gt;(&lt;/span&gt;&lt;span class='varop'&gt;&amp;amp;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class='layout'&gt;)&lt;/span&gt;&lt;span class='layout'&gt;)&lt;/span&gt;
&lt;span class='keyword'&gt;import&lt;/span&gt; &lt;span class='conid'&gt;Data&lt;/span&gt;&lt;span class='varop'&gt;.&lt;/span&gt;&lt;span class='conid'&gt;List&lt;/span&gt; &lt;span class='layout'&gt;(&lt;/span&gt;&lt;span class='varid'&gt;groupBy&lt;/span&gt;&lt;span class='layout'&gt;,&lt;/span&gt; &lt;span class='varid'&gt;sort&lt;/span&gt;&lt;span class='layout'&gt;)&lt;/span&gt;
&lt;span class='keyword'&gt;import&lt;/span&gt; &lt;span class='conid'&gt;Test&lt;/span&gt;&lt;span class='varop'&gt;.&lt;/span&gt;&lt;span class='conid'&gt;HUnit&lt;/span&gt;

&lt;span class='keyword'&gt;type&lt;/span&gt; &lt;span class='conid'&gt;Portion&lt;/span&gt;  &lt;span class='keyglyph'&gt;=&lt;/span&gt; &lt;span class='conid'&gt;Double&lt;/span&gt;      
&lt;span class='keyword'&gt;type&lt;/span&gt; &lt;span class='conid'&gt;Count&lt;/span&gt;    &lt;span class='keyglyph'&gt;=&lt;/span&gt; &lt;span class='conid'&gt;Portion&lt;/span&gt;     
&lt;span class='keyword'&gt;type&lt;/span&gt; &lt;span class='conid'&gt;Price&lt;/span&gt;    &lt;span class='keyglyph'&gt;=&lt;/span&gt; &lt;span class='conid'&gt;Double&lt;/span&gt;      
&lt;span class='keyword'&gt;type&lt;/span&gt; &lt;span class='conid'&gt;Name&lt;/span&gt;     &lt;span class='keyglyph'&gt;=&lt;/span&gt; &lt;span class='conid'&gt;String&lt;/span&gt;      

&lt;span class='keyword'&gt;data&lt;/span&gt; &lt;span class='conid'&gt;PricingRule&lt;/span&gt;
    &lt;span class='keyglyph'&gt;=&lt;/span&gt; &lt;span class='conid'&gt;Per&lt;/span&gt; &lt;span class='conid'&gt;Portion&lt;/span&gt; &lt;span class='conid'&gt;Price&lt;/span&gt;     
    &lt;span class='keyglyph'&gt;|&lt;/span&gt; &lt;span class='conid'&gt;For&lt;/span&gt; &lt;span class='conid'&gt;Count&lt;/span&gt; &lt;span class='conid'&gt;Price&lt;/span&gt; &lt;span class='conid'&gt;Price&lt;/span&gt; 
  &lt;span class='keyword'&gt;deriving&lt;/span&gt; &lt;span class='layout'&gt;(&lt;/span&gt;&lt;span class='conid'&gt;Eq&lt;/span&gt;&lt;span class='layout'&gt;,&lt;/span&gt; &lt;span class='conid'&gt;Ord&lt;/span&gt;&lt;span class='layout'&gt;,&lt;/span&gt; &lt;span class='conid'&gt;Read&lt;/span&gt;&lt;span class='layout'&gt;,&lt;/span&gt; &lt;span class='conid'&gt;Show&lt;/span&gt;&lt;span class='layout'&gt;)&lt;/span&gt;

&lt;span class='keyword'&gt;data&lt;/span&gt; &lt;span class='conid'&gt;Good&lt;/span&gt;
    &lt;span class='keyglyph'&gt;=&lt;/span&gt; &lt;span class='conid'&gt;G&lt;/span&gt; &lt;span class='layout'&gt;{&lt;/span&gt; &lt;span class='varid'&gt;name&lt;/span&gt; &lt;span class='keyglyph'&gt;::&lt;/span&gt; &lt;span class='conid'&gt;Name&lt;/span&gt;&lt;span class='layout'&gt;,&lt;/span&gt; &lt;span class='varid'&gt;quantity&lt;/span&gt; &lt;span class='keyglyph'&gt;::&lt;/span&gt; &lt;span class='varop'&gt;!&lt;/span&gt;&lt;span class='conid'&gt;Portion&lt;/span&gt;&lt;span class='layout'&gt;,&lt;/span&gt; &lt;span class='varid'&gt;rule&lt;/span&gt; &lt;span class='keyglyph'&gt;::&lt;/span&gt; &lt;span class='conid'&gt;PricingRule&lt;/span&gt; &lt;span class='layout'&gt;}&lt;/span&gt;
  &lt;span class='keyword'&gt;deriving&lt;/span&gt; &lt;span class='layout'&gt;(&lt;/span&gt;&lt;span class='conid'&gt;Eq&lt;/span&gt;&lt;span class='layout'&gt;,&lt;/span&gt; &lt;span class='conid'&gt;Ord&lt;/span&gt;&lt;span class='layout'&gt;,&lt;/span&gt; &lt;span class='conid'&gt;Read&lt;/span&gt;&lt;span class='layout'&gt;,&lt;/span&gt; &lt;span class='conid'&gt;Show&lt;/span&gt;&lt;span class='layout'&gt;)&lt;/span&gt;

&lt;span class='varid'&gt;per&lt;/span&gt; &lt;span class='varid'&gt;nm&lt;/span&gt; &lt;span class='varid'&gt;y&lt;/span&gt; &lt;span class='varid'&gt;x&lt;/span&gt; &lt;span class='varid'&gt;p&lt;/span&gt;  &lt;span class='keyglyph'&gt;=&lt;/span&gt; &lt;span class='conid'&gt;G&lt;/span&gt; &lt;span class='varid'&gt;nm&lt;/span&gt; &lt;span class='varid'&gt;y&lt;/span&gt; &lt;span class='layout'&gt;(&lt;/span&gt;&lt;span class='conid'&gt;Per&lt;/span&gt; &lt;span class='varid'&gt;x&lt;/span&gt; &lt;span class='varid'&gt;p&lt;/span&gt;&lt;span class='layout'&gt;)&lt;/span&gt;     
&lt;span class='varid'&gt;each&lt;/span&gt; &lt;span class='varid'&gt;nm&lt;/span&gt; &lt;span class='varid'&gt;p&lt;/span&gt;     &lt;span class='keyglyph'&gt;=&lt;/span&gt; &lt;span class='conid'&gt;G&lt;/span&gt; &lt;span class='varid'&gt;nm&lt;/span&gt; &lt;span class='num'&gt;1&lt;/span&gt; &lt;span class='layout'&gt;(&lt;/span&gt;&lt;span class='conid'&gt;For&lt;/span&gt; &lt;span class='num'&gt;1&lt;/span&gt; &lt;span class='varid'&gt;p&lt;/span&gt; &lt;span class='varid'&gt;p&lt;/span&gt;&lt;span class='layout'&gt;)&lt;/span&gt;   
&lt;span class='varid'&gt;for&lt;/span&gt; &lt;span class='varid'&gt;nm&lt;/span&gt; &lt;span class='varid'&gt;n&lt;/span&gt; &lt;span class='varid'&gt;p&lt;/span&gt;    &lt;span class='keyglyph'&gt;=&lt;/span&gt; &lt;span class='conid'&gt;G&lt;/span&gt; &lt;span class='varid'&gt;nm&lt;/span&gt; &lt;span class='num'&gt;1&lt;/span&gt; &lt;span class='layout'&gt;(&lt;/span&gt;&lt;span class='conid'&gt;For&lt;/span&gt; &lt;span class='varid'&gt;n&lt;/span&gt; &lt;span class='varid'&gt;p&lt;/span&gt; &lt;span class='varid'&gt;p&lt;/span&gt;&lt;span class='layout'&gt;)&lt;/span&gt;   
&lt;span class='varid'&gt;bogo&lt;/span&gt;          &lt;span class='keyglyph'&gt;=&lt;/span&gt; &lt;span class='varid'&gt;flip&lt;/span&gt; &lt;span class='varid'&gt;bngo&lt;/span&gt; &lt;span class='num'&gt;1&lt;/span&gt;          
&lt;span class='varid'&gt;btgo&lt;/span&gt;          &lt;span class='keyglyph'&gt;=&lt;/span&gt; &lt;span class='varid'&gt;flip&lt;/span&gt; &lt;span class='varid'&gt;bngo&lt;/span&gt; &lt;span class='num'&gt;2&lt;/span&gt;          
&lt;span class='varid'&gt;bngo&lt;/span&gt; &lt;span class='varid'&gt;nm&lt;/span&gt; &lt;span class='varid'&gt;n&lt;/span&gt; &lt;span class='varid'&gt;p&lt;/span&gt;   &lt;span class='keyglyph'&gt;=&lt;/span&gt; &lt;span class='conid'&gt;G&lt;/span&gt; &lt;span class='varid'&gt;nm&lt;/span&gt; &lt;span class='num'&gt;1&lt;/span&gt; &lt;span class='layout'&gt;(&lt;/span&gt;&lt;span class='conid'&gt;For&lt;/span&gt; &lt;span class='varid'&gt;n'&lt;/span&gt; &lt;span class='varid'&gt;np&lt;/span&gt; &lt;span class='varid'&gt;p&lt;/span&gt;&lt;span class='layout'&gt;)&lt;/span&gt; 
                &lt;span class='keyword'&gt;where&lt;/span&gt; &lt;span class='layout'&gt;(&lt;/span&gt;&lt;span class='varid'&gt;n'&lt;/span&gt;&lt;span class='layout'&gt;,&lt;/span&gt; &lt;span class='varid'&gt;np&lt;/span&gt;&lt;span class='layout'&gt;)&lt;/span&gt; &lt;span class='keyglyph'&gt;=&lt;/span&gt; &lt;span class='layout'&gt;(&lt;/span&gt;&lt;span class='varid'&gt;n&lt;/span&gt; &lt;span class='varop'&gt;+&lt;/span&gt; &lt;span class='num'&gt;1&lt;/span&gt;&lt;span class='layout'&gt;,&lt;/span&gt; &lt;span class='varid'&gt;p&lt;/span&gt; &lt;span class='varop'&gt;*&lt;/span&gt; &lt;span class='varid'&gt;n&lt;/span&gt;&lt;span class='layout'&gt;)&lt;/span&gt;

&lt;span class='varid'&gt;checkout&lt;/span&gt; &lt;span class='keyglyph'&gt;::&lt;/span&gt; &lt;span class='keyglyph'&gt;[&lt;/span&gt;&lt;span class='conid'&gt;Good&lt;/span&gt;&lt;span class='keyglyph'&gt;]&lt;/span&gt; &lt;span class='keyglyph'&gt;-&amp;gt;&lt;/span&gt; &lt;span class='conid'&gt;Price&lt;/span&gt;
&lt;span class='varid'&gt;checkout&lt;/span&gt; &lt;span class='keyglyph'&gt;=&lt;/span&gt;
    &lt;span class='varid'&gt;checkoutBy&lt;/span&gt; &lt;span class='varop'&gt;$&lt;/span&gt; &lt;span class='varid'&gt;sum&lt;/span&gt; &lt;span class='varop'&gt;.&lt;/span&gt; &lt;span class='varid'&gt;map&lt;/span&gt; &lt;span class='varid'&gt;price&lt;/span&gt;

&lt;span class='varid'&gt;subtotal&lt;/span&gt; &lt;span class='keyglyph'&gt;::&lt;/span&gt; &lt;span class='keyglyph'&gt;[&lt;/span&gt;&lt;span class='conid'&gt;Good&lt;/span&gt;&lt;span class='keyglyph'&gt;]&lt;/span&gt; &lt;span class='keyglyph'&gt;-&amp;gt;&lt;/span&gt; &lt;span class='keyglyph'&gt;[&lt;/span&gt;&lt;span class='layout'&gt;(&lt;/span&gt;&lt;span class='layout'&gt;(&lt;/span&gt;&lt;span class='conid'&gt;Portion&lt;/span&gt;&lt;span class='layout'&gt;,&lt;/span&gt; &lt;span class='conid'&gt;Name&lt;/span&gt;&lt;span class='layout'&gt;)&lt;/span&gt;&lt;span class='layout'&gt;,&lt;/span&gt; &lt;span class='conid'&gt;Price&lt;/span&gt;&lt;span class='layout'&gt;)&lt;/span&gt;&lt;span class='keyglyph'&gt;]&lt;/span&gt;
&lt;span class='varid'&gt;subtotal&lt;/span&gt; &lt;span class='keyglyph'&gt;=&lt;/span&gt;
    &lt;span class='varid'&gt;checkoutBy&lt;/span&gt; &lt;span class='varop'&gt;$&lt;/span&gt; &lt;span class='varid'&gt;map&lt;/span&gt; &lt;span class='layout'&gt;(&lt;/span&gt;&lt;span class='layout'&gt;(&lt;/span&gt;&lt;span class='varid'&gt;quantity&lt;/span&gt; &lt;span class='varop'&gt;&amp;amp;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class='varid'&gt;name&lt;/span&gt;&lt;span class='layout'&gt;)&lt;/span&gt; &lt;span class='varop'&gt;&amp;amp;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class='varid'&gt;price&lt;/span&gt;&lt;span class='layout'&gt;)&lt;/span&gt;

&lt;span class='varid'&gt;checkoutBy&lt;/span&gt; &lt;span class='keyglyph'&gt;::&lt;/span&gt; &lt;span class='layout'&gt;(&lt;/span&gt;&lt;span class='keyglyph'&gt;[&lt;/span&gt;&lt;span class='conid'&gt;Good&lt;/span&gt;&lt;span class='keyglyph'&gt;]&lt;/span&gt; &lt;span class='keyglyph'&gt;-&amp;gt;&lt;/span&gt; &lt;span class='varid'&gt;a&lt;/span&gt;&lt;span class='layout'&gt;)&lt;/span&gt; &lt;span class='keyglyph'&gt;-&amp;gt;&lt;/span&gt; &lt;span class='keyglyph'&gt;[&lt;/span&gt;&lt;span class='conid'&gt;Good&lt;/span&gt;&lt;span class='keyglyph'&gt;]&lt;/span&gt; &lt;span class='keyglyph'&gt;-&amp;gt;&lt;/span&gt; &lt;span class='varid'&gt;a&lt;/span&gt;
&lt;span class='varid'&gt;checkoutBy&lt;/span&gt; &lt;span class='varid'&gt;f&lt;/span&gt; &lt;span class='keyglyph'&gt;=&lt;/span&gt;
    &lt;span class='varid'&gt;f&lt;/span&gt; &lt;span class='varop'&gt;.&lt;/span&gt; &lt;span class='varid'&gt;map&lt;/span&gt; &lt;span class='layout'&gt;(&lt;/span&gt;&lt;span class='varid'&gt;foldl1&lt;/span&gt; &lt;span class='varid'&gt;combine&lt;/span&gt;&lt;span class='layout'&gt;)&lt;/span&gt; &lt;span class='varop'&gt;.&lt;/span&gt; &lt;span class='varid'&gt;groupByName&lt;/span&gt; &lt;span class='varop'&gt;.&lt;/span&gt; &lt;span class='varid'&gt;sort&lt;/span&gt;
  &lt;span class='keyword'&gt;where&lt;/span&gt;
    &lt;span class='varid'&gt;groupByName&lt;/span&gt; &lt;span class='keyglyph'&gt;=&lt;/span&gt; &lt;span class='varid'&gt;groupBy&lt;/span&gt; &lt;span class='layout'&gt;(&lt;/span&gt;&lt;span class='keyglyph'&gt;\&lt;/span&gt;&lt;span class='varid'&gt;g1&lt;/span&gt; &lt;span class='varid'&gt;g2&lt;/span&gt; &lt;span class='keyglyph'&gt;-&amp;gt;&lt;/span&gt; &lt;span class='varid'&gt;name&lt;/span&gt; &lt;span class='varid'&gt;g1&lt;/span&gt; &lt;span class='varop'&gt;==&lt;/span&gt; &lt;span class='varid'&gt;name&lt;/span&gt; &lt;span class='varid'&gt;g2&lt;/span&gt;&lt;span class='layout'&gt;)&lt;/span&gt;

&lt;span class='varid'&gt;price&lt;/span&gt; &lt;span class='keyglyph'&gt;::&lt;/span&gt; &lt;span class='conid'&gt;Good&lt;/span&gt; &lt;span class='keyglyph'&gt;-&amp;gt;&lt;/span&gt; &lt;span class='conid'&gt;Price&lt;/span&gt;
&lt;span class='varid'&gt;price&lt;/span&gt; &lt;span class='layout'&gt;(&lt;/span&gt;&lt;span class='conid'&gt;G&lt;/span&gt; &lt;span class='varid'&gt;nm&lt;/span&gt; &lt;span class='varid'&gt;y&lt;/span&gt; &lt;span class='layout'&gt;(&lt;/span&gt;&lt;span class='conid'&gt;Per&lt;/span&gt; &lt;span class='varid'&gt;x&lt;/span&gt; &lt;span class='varid'&gt;p&lt;/span&gt;&lt;span class='layout'&gt;)&lt;/span&gt;&lt;span class='layout'&gt;)&lt;/span&gt;    &lt;span class='keyglyph'&gt;=&lt;/span&gt;  &lt;span class='varid'&gt;y&lt;/span&gt; &lt;span class='varop'&gt;*&lt;/span&gt; &lt;span class='varid'&gt;p&lt;/span&gt; &lt;span class='varop'&gt;/&lt;/span&gt; &lt;span class='varid'&gt;x&lt;/span&gt;
&lt;span class='varid'&gt;price&lt;/span&gt; &lt;span class='layout'&gt;(&lt;/span&gt;&lt;span class='conid'&gt;G&lt;/span&gt; &lt;span class='varid'&gt;nm&lt;/span&gt; &lt;span class='varid'&gt;m&lt;/span&gt; &lt;span class='layout'&gt;(&lt;/span&gt;&lt;span class='conid'&gt;For&lt;/span&gt; &lt;span class='varid'&gt;n&lt;/span&gt; &lt;span class='varid'&gt;p&lt;/span&gt; &lt;span class='varid'&gt;p2&lt;/span&gt;&lt;span class='layout'&gt;)&lt;/span&gt;&lt;span class='layout'&gt;)&lt;/span&gt; &lt;span class='keyglyph'&gt;=&lt;/span&gt; &lt;span class='layout'&gt;(&lt;/span&gt;&lt;span class='varid'&gt;m&lt;/span&gt; &lt;span class='comment'&gt;-&lt;/span&gt; &lt;span class='varid'&gt;r&lt;/span&gt;&lt;span class='layout'&gt;)&lt;/span&gt; &lt;span class='varop'&gt;*&lt;/span&gt; &lt;span class='varid'&gt;p&lt;/span&gt; &lt;span class='varop'&gt;/&lt;/span&gt; &lt;span class='varid'&gt;n&lt;/span&gt; &lt;span class='varop'&gt;+&lt;/span&gt; &lt;span class='varid'&gt;r&lt;/span&gt; &lt;span class='varop'&gt;*&lt;/span&gt; &lt;span class='varid'&gt;p2&lt;/span&gt;
  &lt;span class='keyword'&gt;where&lt;/span&gt;
    &lt;span class='varid'&gt;r&lt;/span&gt; &lt;span class='keyglyph'&gt;=&lt;/span&gt; &lt;span class='varid'&gt;fromIntegral&lt;/span&gt; &lt;span class='varop'&gt;$&lt;/span&gt; &lt;span class='varid'&gt;round&lt;/span&gt; &lt;span class='varid'&gt;m&lt;/span&gt; &lt;span class='varop'&gt;`rem`&lt;/span&gt; &lt;span class='varid'&gt;round&lt;/span&gt; &lt;span class='varid'&gt;n&lt;/span&gt;

&lt;span class='varid'&gt;combine&lt;/span&gt; &lt;span class='keyglyph'&gt;::&lt;/span&gt; &lt;span class='conid'&gt;Good&lt;/span&gt; &lt;span class='keyglyph'&gt;-&amp;gt;&lt;/span&gt; &lt;span class='conid'&gt;Good&lt;/span&gt; &lt;span class='keyglyph'&gt;-&amp;gt;&lt;/span&gt; &lt;span class='conid'&gt;Good&lt;/span&gt;
&lt;span class='varid'&gt;combine&lt;/span&gt; &lt;span class='varid'&gt;g1&lt;/span&gt;&lt;span class='keyglyph'&gt;@&lt;/span&gt;&lt;span class='layout'&gt;(&lt;/span&gt;&lt;span class='conid'&gt;G&lt;/span&gt; &lt;span class='varid'&gt;nm&lt;/span&gt; &lt;span class='varid'&gt;x&lt;/span&gt; &lt;span class='varid'&gt;rule&lt;/span&gt;&lt;span class='layout'&gt;)&lt;/span&gt; &lt;span class='varid'&gt;g2&lt;/span&gt;&lt;span class='keyglyph'&gt;@&lt;/span&gt;&lt;span class='layout'&gt;(&lt;/span&gt;&lt;span class='conid'&gt;G&lt;/span&gt; &lt;span class='varid'&gt;nm2&lt;/span&gt; &lt;span class='varid'&gt;x2&lt;/span&gt; &lt;span class='varid'&gt;rule2&lt;/span&gt;&lt;span class='layout'&gt;)&lt;/span&gt;
    &lt;span class='keyglyph'&gt;|&lt;/span&gt; &lt;span class='varid'&gt;nm&lt;/span&gt; &lt;span class='varop'&gt;/=&lt;/span&gt; &lt;span class='varid'&gt;nm2&lt;/span&gt; &lt;span class='varop'&gt;||&lt;/span&gt; &lt;span class='varid'&gt;rule&lt;/span&gt; &lt;span class='varop'&gt;/=&lt;/span&gt; &lt;span class='varid'&gt;rule2&lt;/span&gt;
    &lt;span class='keyglyph'&gt;=&lt;/span&gt; &lt;span class='varid'&gt;error&lt;/span&gt; &lt;span class='varop'&gt;$&lt;/span&gt; &lt;span class='str'&gt;"can't combine incompatible goods "&lt;/span&gt; &lt;span class='varop'&gt;++&lt;/span&gt; &lt;span class='varid'&gt;show&lt;/span&gt; &lt;span class='keyglyph'&gt;[&lt;/span&gt;&lt;span class='varid'&gt;g1&lt;/span&gt;&lt;span class='layout'&gt;,&lt;/span&gt; &lt;span class='varid'&gt;g2&lt;/span&gt;&lt;span class='keyglyph'&gt;]&lt;/span&gt;
    &lt;span class='keyglyph'&gt;|&lt;/span&gt; &lt;span class='varid'&gt;otherwise&lt;/span&gt;
    &lt;span class='keyglyph'&gt;=&lt;/span&gt; &lt;span class='conid'&gt;G&lt;/span&gt; &lt;span class='varid'&gt;nm&lt;/span&gt; &lt;span class='layout'&gt;(&lt;/span&gt;&lt;span class='varid'&gt;x&lt;/span&gt; &lt;span class='varop'&gt;+&lt;/span&gt; &lt;span class='varid'&gt;x2&lt;/span&gt;&lt;span class='layout'&gt;)&lt;/span&gt; &lt;span class='varid'&gt;rule&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

	&lt;p&gt;Read on for an explanation of the code and my unit tests.&lt;/p&gt;&lt;h3&gt;What&amp;#8217;s going on in there&lt;/h3&gt;


	&lt;p&gt;Pricing rules are represented by the &lt;em&gt;PricingRule&lt;/em&gt; data type, which
has two forms.  The &lt;em&gt;Per&lt;/em&gt; form is used to price continuous goods (like
bulk oats) at a rate of &lt;em&gt;x&lt;/em&gt; portions per price &lt;em&gt;p&lt;/em&gt;.  The &lt;em&gt;For&lt;/em&gt; form is
used to price discrete goods, &lt;em&gt;m&lt;/em&gt; goods for price &lt;em&gt;p&lt;/em&gt;, with the
remaining goods priced at &lt;em&gt;p2&lt;/em&gt; each.  This form handles &amp;#8220;&lt;em&gt;x&lt;/em&gt;-each,&amp;#8221; 
&amp;#8220;three-for-a-dollar,&amp;#8221; and &amp;#8220;buy-one-get-one-free&amp;#8221; pricing styles.&lt;/p&gt;


	&lt;p&gt;To simplify use of the pricing rules, I defined a small collection of
helpers to construct goods: &lt;em&gt;per&lt;/em&gt;, &lt;em&gt;each&lt;/em&gt;, &lt;em&gt;for&lt;/em&gt;, &lt;em&gt;bogo&lt;/em&gt;, &lt;em&gt;btgo&lt;/em&gt;,
&lt;em&gt;bngo&lt;/em&gt;.  (The last two are buy-two-get-one and buy-&lt;em&gt;n&lt;/em&gt;-get-one.)  Each
takes a good&amp;#8217;s name as its first parameter and then takes the
appropriate price parameters.  The result is a newly constructed &lt;em&gt;Good&lt;/em&gt;.&lt;/p&gt;


	&lt;p&gt;The main function of interest is &lt;em&gt;checkout&lt;/em&gt;, which takes a list of
goods and then computes its checkout price.  A variant of &lt;em&gt;checkout&lt;/em&gt; is
&lt;em&gt;subtotal&lt;/em&gt;, which computes the subtotals for each kind of item. Both
of these are specializations of the generalized checkout function
&lt;em&gt;checkoutBy&lt;/em&gt;, which collects the goods into like-kinded groups,
combines each group into a representative composite good, and then
calls the provided checkout-rule function &lt;em&gt;f&lt;/em&gt; to price the list of
composite goods.  One definition of &lt;em&gt;f&lt;/em&gt; gives &lt;em&gt;checkout&lt;/em&gt;; another
gives &lt;em&gt;subtotal&lt;/em&gt;.&lt;/p&gt;


	&lt;p&gt;Goods are priced using the &lt;em&gt;price&lt;/em&gt; function, which applies a
good&amp;#8217;s pricing rule to the good&amp;#8217;s quantity.  My version of &lt;em&gt;price&lt;/em&gt;
interprets the rules strictly.  You must purchase three of the same
good, for example, to earn a discount on a buy-two-get-one-free rule.
(A more generous person could define a &lt;em&gt;lenientPrice&lt;/em&gt; function to
interpret the rules more charitably.)&lt;/p&gt;


	&lt;p&gt;Finally, the &lt;em&gt;combine&lt;/em&gt; function takes two goods of the same kind and
returns an equivalent composite good.  (It is an error to combine
goods of different kinds.)  For instance, if you combine 1.5 portions
of oats with 0.5 portions of oats, you will get back 2.0 portions of
oats.&lt;/p&gt;


	&lt;p&gt;And that&amp;#8217;s all there is to it.&lt;/p&gt;


	&lt;h3&gt;Unit tests&lt;/h3&gt;


	&lt;p&gt;These are my unit tests:&lt;/p&gt;


&lt;div class="typocode"&gt;&lt;pre&gt;&lt;code class="typocode_haskell "&gt;&lt;span class='comment'&gt;{-
                      *** Unit tests ***

             *SupermarketPricing&amp;gt; runTestTT tests
             Cases: 16  Tried: 16  Errors: 0  Failures: 0
-}&lt;/span&gt;

&lt;span class='varid'&gt;tests&lt;/span&gt; &lt;span class='keyglyph'&gt;=&lt;/span&gt; &lt;span class='varid'&gt;test&lt;/span&gt;
    &lt;span class='keyglyph'&gt;[&lt;/span&gt; &lt;span class='comment'&gt;{- Checkout tests -}&lt;/span&gt;

   &lt;span class='comment'&gt;-- test name               computed result           expected&lt;/span&gt;

      &lt;span class='str'&gt;"1x e99"&lt;/span&gt;             &lt;span class='varop'&gt;~:&lt;/span&gt; &lt;span class='varid'&gt;corep&lt;/span&gt; &lt;span class='num'&gt;1&lt;/span&gt; &lt;span class='varid'&gt;e99&lt;/span&gt;           &lt;span class='varop'&gt;~?=&lt;/span&gt;   &lt;span class='num'&gt;0.99&lt;/span&gt;
    &lt;span class='layout'&gt;,&lt;/span&gt; &lt;span class='str'&gt;"2x e99"&lt;/span&gt;             &lt;span class='varop'&gt;~:&lt;/span&gt; &lt;span class='varid'&gt;corep&lt;/span&gt; &lt;span class='num'&gt;2&lt;/span&gt; &lt;span class='varid'&gt;e99&lt;/span&gt;           &lt;span class='varop'&gt;~?=&lt;/span&gt;   &lt;span class='num'&gt;1.98&lt;/span&gt;
    &lt;span class='layout'&gt;,&lt;/span&gt; &lt;span class='str'&gt;"e99 e100"&lt;/span&gt;           &lt;span class='varop'&gt;~:&lt;/span&gt; &lt;span class='varid'&gt;co&lt;/span&gt; &lt;span class='keyglyph'&gt;[&lt;/span&gt;&lt;span class='varid'&gt;e99&lt;/span&gt;&lt;span class='layout'&gt;,&lt;/span&gt; &lt;span class='varid'&gt;e100&lt;/span&gt;&lt;span class='keyglyph'&gt;]&lt;/span&gt;        &lt;span class='varop'&gt;~?=&lt;/span&gt;   &lt;span class='num'&gt;1.99&lt;/span&gt;
    &lt;span class='layout'&gt;,&lt;/span&gt; &lt;span class='str'&gt;"1x bogo99"&lt;/span&gt;          &lt;span class='varop'&gt;~:&lt;/span&gt; &lt;span class='varid'&gt;corep&lt;/span&gt; &lt;span class='num'&gt;1&lt;/span&gt; &lt;span class='varid'&gt;b99&lt;/span&gt;           &lt;span class='varop'&gt;~?=&lt;/span&gt;   &lt;span class='num'&gt;0.99&lt;/span&gt;
    &lt;span class='layout'&gt;,&lt;/span&gt; &lt;span class='str'&gt;"2x bogo99"&lt;/span&gt;          &lt;span class='varop'&gt;~:&lt;/span&gt; &lt;span class='varid'&gt;corep&lt;/span&gt; &lt;span class='num'&gt;2&lt;/span&gt; &lt;span class='varid'&gt;b99&lt;/span&gt;           &lt;span class='varop'&gt;~?=&lt;/span&gt;   &lt;span class='num'&gt;0.99&lt;/span&gt;
    &lt;span class='layout'&gt;,&lt;/span&gt; &lt;span class='str'&gt;"3x bogo99"&lt;/span&gt;          &lt;span class='varop'&gt;~:&lt;/span&gt; &lt;span class='varid'&gt;corep&lt;/span&gt; &lt;span class='num'&gt;3&lt;/span&gt; &lt;span class='varid'&gt;b99&lt;/span&gt;           &lt;span class='varop'&gt;~?=&lt;/span&gt;   &lt;span class='num'&gt;1.98&lt;/span&gt;
    &lt;span class='layout'&gt;,&lt;/span&gt; &lt;span class='str'&gt;"2x bogo99, split"&lt;/span&gt;   &lt;span class='varop'&gt;~:&lt;/span&gt; &lt;span class='varid'&gt;co&lt;/span&gt; &lt;span class='keyglyph'&gt;[&lt;/span&gt;&lt;span class='varid'&gt;b99&lt;/span&gt;&lt;span class='layout'&gt;,&lt;/span&gt; &lt;span class='varid'&gt;e100&lt;/span&gt;&lt;span class='layout'&gt;,&lt;/span&gt; &lt;span class='varid'&gt;b99&lt;/span&gt;&lt;span class='keyglyph'&gt;]&lt;/span&gt;   &lt;span class='varop'&gt;~?=&lt;/span&gt;   &lt;span class='num'&gt;1.99&lt;/span&gt;
    &lt;span class='layout'&gt;,&lt;/span&gt; &lt;span class='str'&gt;"1x btgo33"&lt;/span&gt;          &lt;span class='varop'&gt;~:&lt;/span&gt; &lt;span class='varid'&gt;corep&lt;/span&gt; &lt;span class='num'&gt;1&lt;/span&gt; &lt;span class='varid'&gt;t33&lt;/span&gt;           &lt;span class='varop'&gt;~?=&lt;/span&gt;   &lt;span class='num'&gt;0.33&lt;/span&gt;
    &lt;span class='layout'&gt;,&lt;/span&gt; &lt;span class='str'&gt;"2x btgo33"&lt;/span&gt;          &lt;span class='varop'&gt;~:&lt;/span&gt; &lt;span class='varid'&gt;corep&lt;/span&gt; &lt;span class='num'&gt;2&lt;/span&gt; &lt;span class='varid'&gt;t33&lt;/span&gt;           &lt;span class='varop'&gt;~?=&lt;/span&gt;   &lt;span class='num'&gt;0.66&lt;/span&gt;
    &lt;span class='layout'&gt;,&lt;/span&gt; &lt;span class='str'&gt;"3x btgo33"&lt;/span&gt;          &lt;span class='varop'&gt;~:&lt;/span&gt; &lt;span class='varid'&gt;corep&lt;/span&gt; &lt;span class='num'&gt;3&lt;/span&gt; &lt;span class='varid'&gt;t33&lt;/span&gt;           &lt;span class='varop'&gt;~?=&lt;/span&gt;   &lt;span class='num'&gt;0.66&lt;/span&gt;
    &lt;span class='layout'&gt;,&lt;/span&gt; &lt;span class='str'&gt;"4x btgo33"&lt;/span&gt;          &lt;span class='varop'&gt;~:&lt;/span&gt; &lt;span class='varid'&gt;corep&lt;/span&gt; &lt;span class='num'&gt;4&lt;/span&gt; &lt;span class='varid'&gt;t33&lt;/span&gt;           &lt;span class='varop'&gt;~?=&lt;/span&gt;   &lt;span class='num'&gt;0.99&lt;/span&gt;
    &lt;span class='layout'&gt;,&lt;/span&gt; &lt;span class='str'&gt;"1.0 bulk"&lt;/span&gt;           &lt;span class='varop'&gt;~:&lt;/span&gt; &lt;span class='varid'&gt;co&lt;/span&gt; &lt;span class='keyglyph'&gt;[&lt;/span&gt;&lt;span class='varid'&gt;bulk&lt;/span&gt; &lt;span class='num'&gt;1&lt;/span&gt;&lt;span class='keyglyph'&gt;]&lt;/span&gt;           &lt;span class='varop'&gt;~?=&lt;/span&gt;   &lt;span class='num'&gt;1.00&lt;/span&gt;
    &lt;span class='layout'&gt;,&lt;/span&gt; &lt;span class='str'&gt;"1.5 bulk"&lt;/span&gt;           &lt;span class='varop'&gt;~:&lt;/span&gt; &lt;span class='varid'&gt;co&lt;/span&gt; &lt;span class='keyglyph'&gt;[&lt;/span&gt;&lt;span class='varid'&gt;bulk&lt;/span&gt; &lt;span class='num'&gt;1.5&lt;/span&gt;&lt;span class='keyglyph'&gt;]&lt;/span&gt;         &lt;span class='varop'&gt;~?=&lt;/span&gt;   &lt;span class='num'&gt;1.50&lt;/span&gt;
    &lt;span class='layout'&gt;,&lt;/span&gt; &lt;span class='str'&gt;"1.0 + 1.5 bulk"&lt;/span&gt;     &lt;span class='varop'&gt;~:&lt;/span&gt; &lt;span class='varid'&gt;co&lt;/span&gt; &lt;span class='keyglyph'&gt;[&lt;/span&gt;&lt;span class='varid'&gt;bulk&lt;/span&gt; &lt;span class='num'&gt;1&lt;/span&gt;&lt;span class='layout'&gt;,&lt;/span&gt; &lt;span class='varid'&gt;bulk&lt;/span&gt; &lt;span class='num'&gt;1.5&lt;/span&gt;&lt;span class='keyglyph'&gt;]&lt;/span&gt; &lt;span class='varop'&gt;~?=&lt;/span&gt;   &lt;span class='num'&gt;2.50&lt;/span&gt;

      &lt;span class='comment'&gt;{- Subtotal tests -}&lt;/span&gt;

    &lt;span class='layout'&gt;,&lt;/span&gt; &lt;span class='str'&gt;"sub(e99, 1.5 oats)"&lt;/span&gt; &lt;span class='varop'&gt;~:&lt;/span&gt; &lt;span class='varid'&gt;subtotal&lt;/span&gt; &lt;span class='keyglyph'&gt;[&lt;/span&gt;&lt;span class='varid'&gt;e99&lt;/span&gt;&lt;span class='layout'&gt;,&lt;/span&gt; &lt;span class='varid'&gt;bulk&lt;/span&gt; &lt;span class='num'&gt;1.5&lt;/span&gt;&lt;span class='keyglyph'&gt;]&lt;/span&gt;
                                 &lt;span class='varop'&gt;~?=&lt;/span&gt; &lt;span class='keyglyph'&gt;[&lt;/span&gt; &lt;span class='layout'&gt;(&lt;/span&gt;&lt;span class='layout'&gt;(&lt;/span&gt;&lt;span class='num'&gt;1.0&lt;/span&gt;&lt;span class='layout'&gt;,&lt;/span&gt; &lt;span class='str'&gt;"e99"&lt;/span&gt;&lt;span class='layout'&gt;)&lt;/span&gt;&lt;span class='layout'&gt;,&lt;/span&gt;  &lt;span class='num'&gt;0.99&lt;/span&gt;&lt;span class='layout'&gt;)&lt;/span&gt;
                                     &lt;span class='layout'&gt;,&lt;/span&gt; &lt;span class='layout'&gt;(&lt;/span&gt;&lt;span class='layout'&gt;(&lt;/span&gt;&lt;span class='num'&gt;1.5&lt;/span&gt;&lt;span class='layout'&gt;,&lt;/span&gt; &lt;span class='str'&gt;"oats"&lt;/span&gt;&lt;span class='layout'&gt;)&lt;/span&gt;&lt;span class='layout'&gt;,&lt;/span&gt; &lt;span class='num'&gt;1.50&lt;/span&gt;&lt;span class='layout'&gt;)&lt;/span&gt; &lt;span class='keyglyph'&gt;]&lt;/span&gt;
    &lt;span class='layout'&gt;,&lt;/span&gt; &lt;span class='str'&gt;"sub(1 oats, e99, 1.5 oats)"&lt;/span&gt;
                           &lt;span class='varop'&gt;~:&lt;/span&gt; &lt;span class='varid'&gt;subtotal&lt;/span&gt; &lt;span class='keyglyph'&gt;[&lt;/span&gt;&lt;span class='varid'&gt;bulk&lt;/span&gt; &lt;span class='num'&gt;1&lt;/span&gt;&lt;span class='layout'&gt;,&lt;/span&gt; &lt;span class='varid'&gt;e99&lt;/span&gt;&lt;span class='layout'&gt;,&lt;/span&gt; &lt;span class='varid'&gt;bulk&lt;/span&gt; &lt;span class='num'&gt;1.5&lt;/span&gt;&lt;span class='keyglyph'&gt;]&lt;/span&gt;
                                 &lt;span class='varop'&gt;~?=&lt;/span&gt; &lt;span class='keyglyph'&gt;[&lt;/span&gt; &lt;span class='layout'&gt;(&lt;/span&gt;&lt;span class='layout'&gt;(&lt;/span&gt;&lt;span class='num'&gt;1.0&lt;/span&gt;&lt;span class='layout'&gt;,&lt;/span&gt; &lt;span class='str'&gt;"e99"&lt;/span&gt;&lt;span class='layout'&gt;)&lt;/span&gt;&lt;span class='layout'&gt;,&lt;/span&gt;  &lt;span class='num'&gt;0.99&lt;/span&gt;&lt;span class='layout'&gt;)&lt;/span&gt;
                                     &lt;span class='layout'&gt;,&lt;/span&gt; &lt;span class='layout'&gt;(&lt;/span&gt;&lt;span class='layout'&gt;(&lt;/span&gt;&lt;span class='num'&gt;2.5&lt;/span&gt;&lt;span class='layout'&gt;,&lt;/span&gt; &lt;span class='str'&gt;"oats"&lt;/span&gt;&lt;span class='layout'&gt;)&lt;/span&gt;&lt;span class='layout'&gt;,&lt;/span&gt; &lt;span class='num'&gt;2.50&lt;/span&gt;&lt;span class='layout'&gt;)&lt;/span&gt; &lt;span class='keyglyph'&gt;]&lt;/span&gt;
    &lt;span class='keyglyph'&gt;]&lt;/span&gt;
  &lt;span class='keyword'&gt;where&lt;/span&gt;

    &lt;span class='comment'&gt;-- shorthand defs for functions used commonly in testing&lt;/span&gt;

    &lt;span class='varid'&gt;co&lt;/span&gt;       &lt;span class='keyglyph'&gt;=&lt;/span&gt; &lt;span class='varid'&gt;checkout&lt;/span&gt;
    &lt;span class='varid'&gt;rep&lt;/span&gt;      &lt;span class='keyglyph'&gt;=&lt;/span&gt; &lt;span class='varid'&gt;replicate&lt;/span&gt;
    &lt;span class='varid'&gt;corep&lt;/span&gt; &lt;span class='varid'&gt;n&lt;/span&gt;  &lt;span class='keyglyph'&gt;=&lt;/span&gt; &lt;span class='varid'&gt;co&lt;/span&gt; &lt;span class='varop'&gt;.&lt;/span&gt; &lt;span class='varid'&gt;rep&lt;/span&gt; &lt;span class='varid'&gt;n&lt;/span&gt;

    &lt;span class='comment'&gt;-- goods used in testing&lt;/span&gt;

    &lt;span class='varid'&gt;e99&lt;/span&gt;      &lt;span class='keyglyph'&gt;=&lt;/span&gt; &lt;span class='varid'&gt;each&lt;/span&gt; &lt;span class='str'&gt;"e99"&lt;/span&gt; &lt;span class='num'&gt;0.99&lt;/span&gt;      
    &lt;span class='varid'&gt;e100&lt;/span&gt;     &lt;span class='keyglyph'&gt;=&lt;/span&gt; &lt;span class='varid'&gt;each&lt;/span&gt; &lt;span class='str'&gt;"e100"&lt;/span&gt; &lt;span class='num'&gt;1.00&lt;/span&gt;     
    &lt;span class='varid'&gt;b99&lt;/span&gt;      &lt;span class='keyglyph'&gt;=&lt;/span&gt; &lt;span class='varid'&gt;bogo&lt;/span&gt; &lt;span class='str'&gt;"bogo99"&lt;/span&gt; &lt;span class='num'&gt;0.99&lt;/span&gt;   
    &lt;span class='varid'&gt;t33&lt;/span&gt;      &lt;span class='keyglyph'&gt;=&lt;/span&gt; &lt;span class='varid'&gt;btgo&lt;/span&gt; &lt;span class='str'&gt;"btgo33"&lt;/span&gt; &lt;span class='num'&gt;0.33&lt;/span&gt;   
    &lt;span class='varid'&gt;bulk&lt;/span&gt; &lt;span class='varid'&gt;x&lt;/span&gt;   &lt;span class='keyglyph'&gt;=&lt;/span&gt; &lt;span class='varid'&gt;per&lt;/span&gt; &lt;span class='str'&gt;"oats"&lt;/span&gt; &lt;span class='varid'&gt;x&lt;/span&gt; &lt;span class='num'&gt;1&lt;/span&gt; &lt;span class='num'&gt;1.00&lt;/span&gt;  
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description>
      <pubDate>Fri, 28 Apr 2006 16:30:00 -0400</pubDate>
      <guid isPermaLink="false">urn:uuid:14370b92671076eb61aa9c7450a9be39</guid>
      <author>Tom Moertel</author>
      <link>http://blog.moertel.com/articles/2006/04/28/the-supermarket-pricing-kata-in-haskell</link>
      <category>programming</category>
      <category>haskell</category>
      <category>pittsburgh</category>
      <category>haskell</category>
      <category>katas</category>
      <category>supermarket</category>
      <category>pricing</category>
      <category>coding</category>
      <category>dojo</category>
      <trackback:ping>http://blog.moertel.com/articles/trackback/65</trackback:ping>
    </item>
    <item>
      <title>Perl and Pittsburgh: fun stuff coming your way!</title>
      <description>&lt;p&gt;If you live anywhere near Pittsburgh and are interested in Perl, have
I got a couple of announcements for you:&lt;/p&gt;


	&lt;p&gt;First, the ever-fascinating &lt;a href="http://www.plover.com/blog/"&gt;Mark Jason
Dominus&lt;/a&gt; is speaking &lt;em&gt;tonight&lt;/em&gt; (2006-04-12, now passed) on
&lt;a href="http://pgh.pm.org/archives/006311.html"&gt;Improving Your Perl Code&lt;/a&gt; at
the regular meeting of the &lt;a href="http://pgh.pm.org/"&gt;Pittsburgh Perl
Mongers&lt;/a&gt;.  If you are interested in Perl,
&lt;em&gt;be there&lt;/em&gt;.  (You don&amp;#8217;t need to be a Perl Monger to attend.)  Mark is a
great speaker and wrote one of my favorite Perl books, &lt;a href="http://hop.perl.plover.com/"&gt;Higher Order
Perl&lt;/a&gt;, which puts the &lt;em&gt;fun&lt;/em&gt; in functional
programming.&lt;/p&gt;


	&lt;p&gt;Second, &lt;a href="http://pghpw.org/"&gt;The Pittsburgh Perl Workshop&lt;/a&gt; is &lt;em&gt;on&lt;/em&gt;.  Put a big circle around this date on your
calendar:  Saturday, 23 September 2006.  It&amp;#8217;s
a full day of sleeves-rolled-up Perl fun, all focused on the sweat-inducing theme of &lt;em&gt;Perl At Work.&lt;/em&gt;  To top it off, the Workshop is dirt cheap, especially if you get the 50-percent &amp;#8220;Early Bird&amp;#8221; discount.  (Hint:
register now.)&lt;/p&gt;


	&lt;p&gt;It&amp;#8217;s a fun time for Perl folks in Pittsburgh.&lt;/p&gt;


	&lt;p&gt;&lt;strong&gt;Update:&lt;/strong&gt; MJD gave his talk, and it rocked.  If you missed it, you should find a time machine &lt;em&gt;now&lt;/em&gt; and use it to  take yourself back to Wednesday evening to hear his talk.  Or you could find out when he is speaking in the &lt;em&gt;future&lt;/em&gt; and be there.  Either way, don&amp;#8217;t miss out again.&lt;/p&gt;</description>
      <pubDate>Wed, 12 Apr 2006 12:29:00 -0400</pubDate>
      <guid isPermaLink="false">urn:uuid:1348f7892b0caf6a46177b9d5ca1a242</guid>
      <author>Tom Moertel</author>
      <link>http://blog.moertel.com/articles/2006/04/12/perl-and-pittsburgh-fun-stuff-coming-your-way</link>
      <category>perl</category>
      <category>pittsburgh</category>
      <trackback:ping>http://blog.moertel.com/articles/trackback/63</trackback:ping>
    </item>
    <item>
      <title>The Bowling Game Kata in Haskell</title>
      <description>&lt;p&gt;On the &lt;a href="http://www.wplug.org/"&gt;&lt;span class="caps"&gt;WPLUG&lt;/span&gt;&lt;/a&gt; mailing list I came across a post
about the formation of a &lt;a href="http://www.insomnia-consulting.org/"&gt;Pittsburgh Coding
Dojo&lt;/a&gt;.  The idea is to get a bunch
of hackers together and have them work on solving a challenge
problem with the goal of sharpening their programming skills and
learning from each other.&lt;/p&gt;


	&lt;p&gt;There was a &lt;a href="http://www.jadetower.org/muses/archives/000491.html"&gt;trial meeting on 31 March&lt;/a&gt; that focused on &lt;a href="http://butunclebob.com/ArticleS.UncleBob.TheBowlingGameKata"&gt;The Bowling Game
Kata&lt;/a&gt;.
The challenge was essentially to write some code that scores
a full (ten-frame) game of bowling.  A game is represented by a series
of &amp;#8220;rolls,&amp;#8221; each being the number of pins knocked down by a
roll of the bowling ball.  The scoring function must
determine frame boundaries from the sequence of rolls and score all
ten frames according to the rules of bowling, i.e., taking into account
spares and strikes and the final frame.&lt;/p&gt;


	&lt;p&gt;The challenge sounded like a fun lunch-break problem, and so I
whipped up the following solution in Haskell.  (You might find
it interesting to compare this solution to the Java-based
solutions on the web.)&lt;/p&gt;


&lt;div class="typocode"&gt;&lt;pre&gt;&lt;code class="typocode_haskell "&gt;&lt;span class='comment'&gt;{-
   My solution to "The Bowling Game Kata" 
   Tom Moertel &amp;lt;tom@moertel.com&amp;gt;
   2006-04-05

   See &lt;a href="http://butunclebob.com/ArticleS.UncleBob.TheBowlingGameKata"&gt;http://butunclebob.com/ArticleS.UncleBob.TheBowlingGameKata&lt;/a&gt;
-}&lt;/span&gt;

&lt;span class='keyword'&gt;module&lt;/span&gt; &lt;span class='conid'&gt;Bowling&lt;/span&gt; &lt;span class='layout'&gt;(&lt;/span&gt;&lt;span class='varid'&gt;score&lt;/span&gt;&lt;span class='layout'&gt;)&lt;/span&gt; &lt;span class='keyword'&gt;where&lt;/span&gt;

&lt;span class='keyword'&gt;import&lt;/span&gt; &lt;span class='conid'&gt;Test&lt;/span&gt;&lt;span class='varop'&gt;.&lt;/span&gt;&lt;span class='conid'&gt;HUnit&lt;/span&gt;

&lt;span class='comment'&gt;-- | Compute the score for the list of rolls 'rs'&lt;/span&gt;

&lt;span class='varid'&gt;score&lt;/span&gt; &lt;span class='varid'&gt;rs&lt;/span&gt; &lt;span class='keyglyph'&gt;=&lt;/span&gt; &lt;span class='varid'&gt;sc&lt;/span&gt; &lt;span class='num'&gt;0&lt;/span&gt; &lt;span class='num'&gt;1&lt;/span&gt; &lt;span class='varid'&gt;rs&lt;/span&gt;

&lt;span class='comment'&gt;-- accumulate the score 's' and frame count 'f' while consuming a&lt;/span&gt;
&lt;span class='comment'&gt;-- list of rolls 'rs' one frame at a time&lt;/span&gt;

&lt;span class='varid'&gt;sc&lt;/span&gt; &lt;span class='varid'&gt;s&lt;/span&gt; &lt;span class='num'&gt;11&lt;/span&gt; &lt;span class='keyword'&gt;_&lt;/span&gt;  &lt;span class='keyglyph'&gt;=&lt;/span&gt; &lt;span class='varid'&gt;s&lt;/span&gt;           &lt;span class='comment'&gt;-- frame 11 means all done; return score&lt;/span&gt;
&lt;span class='varid'&gt;sc&lt;/span&gt; &lt;span class='varid'&gt;s&lt;/span&gt; &lt;span class='varid'&gt;f&lt;/span&gt; &lt;span class='varid'&gt;rs&lt;/span&gt;  &lt;span class='keyglyph'&gt;=&lt;/span&gt; &lt;span class='keyword'&gt;case&lt;/span&gt; &lt;span class='varid'&gt;rs&lt;/span&gt; &lt;span class='keyword'&gt;of&lt;/span&gt;  &lt;span class='comment'&gt;-- otherwise, consume the frame &amp;amp; recurse&lt;/span&gt;
    &lt;span class='num'&gt;10&lt;/span&gt;&lt;span class='conop'&gt;:&lt;/span&gt;&lt;span class='varid'&gt;rs'&lt;/span&gt;                &lt;span class='keyglyph'&gt;-&amp;gt;&lt;/span&gt; &lt;span class='varid'&gt;sc'&lt;/span&gt; &lt;span class='num'&gt;3&lt;/span&gt; &lt;span class='varid'&gt;rs'&lt;/span&gt;  &lt;span class='comment'&gt;-- strike&lt;/span&gt;
    &lt;span class='varid'&gt;x&lt;/span&gt;&lt;span class='conop'&gt;:&lt;/span&gt;&lt;span class='varid'&gt;y&lt;/span&gt;&lt;span class='conop'&gt;:&lt;/span&gt;&lt;span class='varid'&gt;rs'&lt;/span&gt; &lt;span class='keyglyph'&gt;|&lt;/span&gt; &lt;span class='varid'&gt;x&lt;/span&gt; &lt;span class='varop'&gt;+&lt;/span&gt; &lt;span class='varid'&gt;y&lt;/span&gt; &lt;span class='varop'&gt;==&lt;/span&gt; &lt;span class='num'&gt;10&lt;/span&gt; &lt;span class='keyglyph'&gt;-&amp;gt;&lt;/span&gt; &lt;span class='varid'&gt;sc'&lt;/span&gt; &lt;span class='num'&gt;3&lt;/span&gt; &lt;span class='varid'&gt;rs'&lt;/span&gt;  &lt;span class='comment'&gt;-- spare&lt;/span&gt;
            &lt;span class='keyglyph'&gt;|&lt;/span&gt; &lt;span class='varid'&gt;otherwise&lt;/span&gt;   &lt;span class='keyglyph'&gt;-&amp;gt;&lt;/span&gt; &lt;span class='varid'&gt;sc'&lt;/span&gt; &lt;span class='num'&gt;2&lt;/span&gt; &lt;span class='varid'&gt;rs'&lt;/span&gt;  &lt;span class='comment'&gt;-- normal&lt;/span&gt;
    &lt;span class='keyword'&gt;_&lt;/span&gt;                     &lt;span class='keyglyph'&gt;-&amp;gt;&lt;/span&gt; &lt;span class='varid'&gt;error&lt;/span&gt; &lt;span class='str'&gt;"ill-formed sequence of rolls"&lt;/span&gt;
  &lt;span class='keyword'&gt;where&lt;/span&gt;
    &lt;span class='comment'&gt;-- accumulate the next 'n' rolls into the score and recurse&lt;/span&gt;
    &lt;span class='varid'&gt;sc'&lt;/span&gt; &lt;span class='varid'&gt;n&lt;/span&gt; &lt;span class='varid'&gt;rs'&lt;/span&gt; &lt;span class='keyglyph'&gt;=&lt;/span&gt; &lt;span class='varid'&gt;sc&lt;/span&gt; &lt;span class='layout'&gt;(&lt;/span&gt;&lt;span class='varid'&gt;s&lt;/span&gt; &lt;span class='varop'&gt;+&lt;/span&gt; &lt;span class='varid'&gt;sum&lt;/span&gt; &lt;span class='layout'&gt;(&lt;/span&gt;&lt;span class='varid'&gt;take&lt;/span&gt; &lt;span class='varid'&gt;n&lt;/span&gt; &lt;span class='varid'&gt;rs&lt;/span&gt;&lt;span class='layout'&gt;)&lt;/span&gt;&lt;span class='layout'&gt;)&lt;/span&gt; &lt;span class='layout'&gt;(&lt;/span&gt;&lt;span class='varid'&gt;f&lt;/span&gt; &lt;span class='varop'&gt;+&lt;/span&gt; &lt;span class='num'&gt;1&lt;/span&gt;&lt;span class='layout'&gt;)&lt;/span&gt; &lt;span class='varid'&gt;rs'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

	&lt;p&gt;Here are my unit tests:&lt;/p&gt;


&lt;div class="typocode"&gt;&lt;pre&gt;&lt;code class="typocode_haskell "&gt;&lt;span class='comment'&gt;{-
                      *** Unit tests ***

             *Bowling&amp;gt; runTestTT tests
             Cases: 9  Tried: 9  Errors: 0  Failures: 0
-}&lt;/span&gt;

&lt;span class='varid'&gt;tests&lt;/span&gt; &lt;span class='keyglyph'&gt;=&lt;/span&gt; &lt;span class='varid'&gt;test&lt;/span&gt;
    &lt;span class='keyglyph'&gt;[&lt;/span&gt; &lt;span class='str'&gt;"gutters"&lt;/span&gt;       &lt;span class='varop'&gt;~:&lt;/span&gt; &lt;span class='varid'&gt;score&lt;/span&gt;  &lt;span class='layout'&gt;(&lt;/span&gt;&lt;span class='varid'&gt;rep&lt;/span&gt; &lt;span class='num'&gt;20&lt;/span&gt;  &lt;span class='num'&gt;0&lt;/span&gt;&lt;span class='layout'&gt;)&lt;/span&gt;          &lt;span class='varop'&gt;~?=&lt;/span&gt;   &lt;span class='num'&gt;0&lt;/span&gt;
    &lt;span class='layout'&gt;,&lt;/span&gt; &lt;span class='str'&gt;"ones"&lt;/span&gt;          &lt;span class='varop'&gt;~:&lt;/span&gt; &lt;span class='varid'&gt;score&lt;/span&gt;  &lt;span class='layout'&gt;(&lt;/span&gt;&lt;span class='varid'&gt;rep&lt;/span&gt; &lt;span class='num'&gt;20&lt;/span&gt;  &lt;span class='num'&gt;1&lt;/span&gt;&lt;span class='layout'&gt;)&lt;/span&gt;          &lt;span class='varop'&gt;~?=&lt;/span&gt;  &lt;span class='num'&gt;20&lt;/span&gt;
    &lt;span class='layout'&gt;,&lt;/span&gt; &lt;span class='str'&gt;"fives"&lt;/span&gt;         &lt;span class='varop'&gt;~:&lt;/span&gt; &lt;span class='varid'&gt;score&lt;/span&gt;  &lt;span class='layout'&gt;(&lt;/span&gt;&lt;span class='varid'&gt;rep&lt;/span&gt; &lt;span class='num'&gt;22&lt;/span&gt;  &lt;span class='num'&gt;5&lt;/span&gt;&lt;span class='layout'&gt;)&lt;/span&gt;          &lt;span class='varop'&gt;~?=&lt;/span&gt; &lt;span class='num'&gt;150&lt;/span&gt;
    &lt;span class='layout'&gt;,&lt;/span&gt; &lt;span class='str'&gt;"strikes"&lt;/span&gt;       &lt;span class='varop'&gt;~:&lt;/span&gt; &lt;span class='varid'&gt;score&lt;/span&gt;  &lt;span class='layout'&gt;(&lt;/span&gt;&lt;span class='varid'&gt;rep&lt;/span&gt; &lt;span class='num'&gt;12&lt;/span&gt; &lt;span class='num'&gt;10&lt;/span&gt;&lt;span class='layout'&gt;)&lt;/span&gt;          &lt;span class='varop'&gt;~?=&lt;/span&gt; &lt;span class='num'&gt;300&lt;/span&gt;
    &lt;span class='layout'&gt;,&lt;/span&gt; &lt;span class='str'&gt;"1 + gutters"&lt;/span&gt;   &lt;span class='varop'&gt;~:&lt;/span&gt; &lt;span class='varid'&gt;score&lt;/span&gt;  &lt;span class='layout'&gt;(&lt;/span&gt;&lt;span class='num'&gt;1&lt;/span&gt; &lt;span class='conop'&gt;:&lt;/span&gt; &lt;span class='varid'&gt;rep&lt;/span&gt; &lt;span class='num'&gt;19&lt;/span&gt; &lt;span class='num'&gt;0&lt;/span&gt;&lt;span class='layout'&gt;)&lt;/span&gt;       &lt;span class='varop'&gt;~?=&lt;/span&gt;   &lt;span class='num'&gt;1&lt;/span&gt;
    &lt;span class='layout'&gt;,&lt;/span&gt; &lt;span class='str'&gt;"first spare"&lt;/span&gt;   &lt;span class='varop'&gt;~:&lt;/span&gt; &lt;span class='varid'&gt;score&lt;/span&gt;  &lt;span class='layout'&gt;(&lt;/span&gt;&lt;span class='num'&gt;5&lt;/span&gt;&lt;span class='conop'&gt;:&lt;/span&gt;&lt;span class='num'&gt;5&lt;/span&gt;&lt;span class='conop'&gt;:&lt;/span&gt;&lt;span class='num'&gt;5&lt;/span&gt; &lt;span class='conop'&gt;:&lt;/span&gt; &lt;span class='varid'&gt;rep&lt;/span&gt; &lt;span class='num'&gt;17&lt;/span&gt; &lt;span class='num'&gt;0&lt;/span&gt;&lt;span class='layout'&gt;)&lt;/span&gt;   &lt;span class='varop'&gt;~?=&lt;/span&gt;  &lt;span class='num'&gt;20&lt;/span&gt;
    &lt;span class='layout'&gt;,&lt;/span&gt; &lt;span class='str'&gt;"first strike"&lt;/span&gt;  &lt;span class='varop'&gt;~:&lt;/span&gt; &lt;span class='varid'&gt;score&lt;/span&gt;  &lt;span class='layout'&gt;(&lt;/span&gt;&lt;span class='num'&gt;10&lt;/span&gt;&lt;span class='conop'&gt;:&lt;/span&gt;&lt;span class='num'&gt;5&lt;/span&gt;&lt;span class='conop'&gt;:&lt;/span&gt;&lt;span class='num'&gt;5&lt;/span&gt; &lt;span class='conop'&gt;:&lt;/span&gt; &lt;span class='varid'&gt;rep&lt;/span&gt; &lt;span class='num'&gt;17&lt;/span&gt; &lt;span class='num'&gt;0&lt;/span&gt;&lt;span class='layout'&gt;)&lt;/span&gt;  &lt;span class='varop'&gt;~?=&lt;/span&gt;  &lt;span class='num'&gt;30&lt;/span&gt;
    &lt;span class='layout'&gt;,&lt;/span&gt; &lt;span class='str'&gt;"last spare"&lt;/span&gt;    &lt;span class='varop'&gt;~:&lt;/span&gt; &lt;span class='varid'&gt;rscore&lt;/span&gt; &lt;span class='layout'&gt;(&lt;/span&gt;&lt;span class='num'&gt;5&lt;/span&gt;&lt;span class='conop'&gt;:&lt;/span&gt;&lt;span class='num'&gt;5&lt;/span&gt;&lt;span class='conop'&gt;:&lt;/span&gt;&lt;span class='num'&gt;5&lt;/span&gt; &lt;span class='conop'&gt;:&lt;/span&gt; &lt;span class='varid'&gt;rep&lt;/span&gt; &lt;span class='num'&gt;18&lt;/span&gt; &lt;span class='num'&gt;0&lt;/span&gt;&lt;span class='layout'&gt;)&lt;/span&gt;   &lt;span class='varop'&gt;~?=&lt;/span&gt;  &lt;span class='num'&gt;15&lt;/span&gt;
    &lt;span class='layout'&gt;,&lt;/span&gt; &lt;span class='str'&gt;"last strike"&lt;/span&gt;   &lt;span class='varop'&gt;~:&lt;/span&gt; &lt;span class='varid'&gt;rscore&lt;/span&gt; &lt;span class='layout'&gt;(&lt;/span&gt;&lt;span class='num'&gt;5&lt;/span&gt;&lt;span class='conop'&gt;:&lt;/span&gt;&lt;span class='num'&gt;5&lt;/span&gt;&lt;span class='conop'&gt;:&lt;/span&gt;&lt;span class='num'&gt;10&lt;/span&gt; &lt;span class='conop'&gt;:&lt;/span&gt; &lt;span class='varid'&gt;rep&lt;/span&gt; &lt;span class='num'&gt;18&lt;/span&gt; &lt;span class='num'&gt;0&lt;/span&gt;&lt;span class='layout'&gt;)&lt;/span&gt;  &lt;span class='varop'&gt;~?=&lt;/span&gt;  &lt;span class='num'&gt;20&lt;/span&gt;
    &lt;span class='keyglyph'&gt;]&lt;/span&gt;
  &lt;span class='keyword'&gt;where&lt;/span&gt;
    &lt;span class='varid'&gt;rep&lt;/span&gt;    &lt;span class='keyglyph'&gt;=&lt;/span&gt; &lt;span class='varid'&gt;replicate&lt;/span&gt;
    &lt;span class='varid'&gt;rscore&lt;/span&gt; &lt;span class='keyglyph'&gt;=&lt;/span&gt; &lt;span class='varid'&gt;score&lt;/span&gt; &lt;span class='varop'&gt;.&lt;/span&gt; &lt;span class='varid'&gt;reverse&lt;/span&gt;  &lt;span class='comment'&gt;-- reverse list and then score it&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

	&lt;p&gt;If you have a little free time, code up a solution in your
favorite language.&lt;/p&gt;</description>
      <pubDate>Wed, 05 Apr 2006 15:18:00 -0400</pubDate>
      <guid isPermaLink="false">urn:uuid:ccebf69a709784ea7b53ff76c77008be</guid>
      <author>Tom Moertel</author>
      <link>http://blog.moertel.com/articles/2006/04/05/the-bowling-game-kata-in-haskell</link>
      <category>programming</category>
      <category>haskell</category>
      <category>pittsburgh</category>
      <category>haskell</category>
      <category>kata</category>
      <category>bowling</category>
      <trackback:ping>http://blog.moertel.com/articles/trackback/61</trackback:ping>
    </item>
    <item>
      <title>Good stuff: Aldo Coffee Company</title>
      <description>&lt;p&gt;I love espresso.  It&amp;#8217;s my favorite way to enjoy coffee.  Even so, I
almost never order espresso in coffee shops because, here in the
United States, very few coffee shops have mastered the exacting
process by which espresso is made.  Dr. Josuma John of the Josuma
Coffee Company &lt;a href="http://www.josuma.com/european.shtml"&gt;writes&lt;/a&gt; that
&amp;#8220;more than 95 percent of North American espresso is poorly made, and,
in fact, undrinkable.&amp;#8221; My experience with Pittsburgh-area coffee
shops in the last decade provides no evidence to refute Dr. John&amp;#8217;s
claim.&lt;/p&gt;


	&lt;p&gt;If espresso in the United States is so bad, why do Americans drink
enough of it to support a Starbucks on every street corner?  The reason
is that Americans drink espresso almost exclusively in the form of
milk-based beverages: cappuccinos, lattes, and mochas.  Milk and
flavored syrups are the main attractions.  Espresso serves only as a
coffee-flavored backdrop in which bitterness, a characteristic
of poorly made espresso, complements the abundant sweetness of milk
laced with sugar syrups.  American coffee-shop owners thus have little
incentive to offer better espresso to their customers &amp;#8211; bad espresso
is good enough.&lt;/p&gt;


	&lt;p&gt;Because of this sad reality, I have developed through hard experience
the following reliable guideline for ordering espresso at American
coffee shops: &lt;em&gt;Don&amp;#8217;t.&lt;/em&gt; The one exception I make is for new coffee
shops, at which I will try a double espresso, just to see what I get.
Almost always, I get a bad espresso, bitter and watery.&lt;/p&gt;


	&lt;p&gt;And that is what I had expected back in April 2005, when I spotted the
brand-new sign for &lt;a href="http://www.aldocoffee.com/"&gt;Aldo Coffee Co.&lt;/a&gt; in my
home town of Mt. Lebanon, Pennsylvania, located in Pittsburgh&amp;#8217;s South
Hills.  I went in, dragging my wife along, and placed my order.&lt;/p&gt;


	&lt;p&gt;Then something unusual happened.  The barista asked me, somewhat
expectantly it seemed, if I drank espresso regularly.  When I
responded, &amp;#8220;Oh my, yes,&amp;#8221; she seemed pleased.  When she followed up by
asking me if I read
&lt;a href="http://groups.google.com/group/alt.coffee"&gt;alt.coffee&lt;/a&gt;, I was stunned.
When I observed that she was timing my shot, my brain actually shut
down for a few seconds while it forcibly recalibrated itself to
accommodate the seemingly impossible: that I was standing in a
coffee shop in my home town, conversing with a barista about
alt.coffee, and mere seconds away from receiving what was very likely
to be &lt;em&gt;good espresso&lt;/em&gt;.&lt;/p&gt;&lt;h3&gt;Good espresso&lt;/h3&gt;


	&lt;p&gt;And, in fact, the espresso was good.  Aldo uses Intelligentsia Coffee
&amp;#38; Tea&amp;#8217;s &lt;a href="http://www.intelligentsiacoffee.com/store/coffee/blends/blackcat"&gt;Black Cat Espresso
Blend&lt;/a&gt;,
which is a great espresso blend, skillfully roasted.  It seems to work well in
commercial espresso environments, where extraction temperatures can be
carefully controlled.  (I think that Black Cat tips into burnt
flavors when pulled too hot.)  All in all, a delightful cup.&lt;/p&gt;


	&lt;p&gt;Since then, I have visited Aldo Coffee Co. regularly, typically at
least once per week, and the espresso has been reliably good.  I would
say that out of five cups, I typically get one that is pretty good,
two that are definitely good, one that approaches greatness, and one
that is great.  I don&amp;#8217;t recall ever having received an undrinkable
cup.  At one time (when their machine was running hot or
Intelligentsia was off their target profile) I did receive a
stretch of cups with emphasized burnt flavors, but still the overall
impression was pretty good.&lt;/p&gt;


	&lt;p&gt;Certainly, there is room for improvement, but with espresso, that is
the way it goes.  Espresso is a tricky beverage, requiring continual
fine tuning and practice.  To put Aldo&amp;#8217;s performance into perspective,
at home, where I can focus on a single customer&amp;#8217;s tastes &amp;#8211; mine &amp;#8211; and
I am working with equipment that I have practiced on for about a
decade, I don&amp;#8217;t do much better: out of five tries I get one pretty
good, one good, two approaching greatness, and one great. (One of the
great shots is pictured in my blog&amp;#8217;s banner.)  At most coffee shops,
five out of five times I get something not worth drinking.&lt;/p&gt;


	&lt;p&gt;The only other contender in Pittsburgh is La Prima Espresso, which
&lt;a href="http://www.tgr.com/weblog/archives/000040.html"&gt;gets the nod of approval from Tea Leaves
guys&lt;/a&gt;, whose tastes I
tend to agree with.  My experience at La Prima, however, has not
matched theirs.  More often than not, the espresso I have been served
was over-extracted.  I have received a good cup on occasion, maybe one
for every five visits, but for the most part I have left disappointed.&lt;/p&gt;


	&lt;p&gt;All of this leads me to the following claim: Aldo Coffee Co. is the
most reliable place I know of to get good espresso in Pittsburgh.&lt;/p&gt;


	&lt;h3&gt; Good service and food&lt;/h3&gt;


	&lt;p&gt;That Aldo&amp;#8217;s owners are willing to invest in making good espresso, a
tricky product that I suspect less than five percent of their
customers appreciate, suggests they are atypically attentive to all of
their offerings.  After all, if you are going to cut corners, why not
start with espresso?&lt;/p&gt;


	&lt;p&gt;The Aldo staff seems to bear out this theory.  Everybody is friendly,
and several baristas know me by name, despite that I&amp;#8217;m not a daily
customer.&lt;/p&gt;


	&lt;p&gt;Their &lt;a href="http://www.aldocoffee.com/panini/index.html"&gt;sandwiches&lt;/a&gt; also
point to something beyond the norm.  I have tried the &amp;#8220;tuna &amp;#38;
artichoke&amp;#8221; and the &amp;#8220;eggplant, prosciutto, &amp;#38; mozzarella,&amp;#8221; and both were
delicious, reminding me of the typical fare in Italian bars, where
they &lt;em&gt;care&lt;/em&gt; about these things.  The breads (from &lt;a href="http://www.mediterrabakehouse.com/"&gt;Mediterra
Bakehouse&lt;/a&gt;) are crusty and
flavorful, balancing with the fillings, neither dominating the other.&lt;/p&gt;


	&lt;p&gt;I am not a dessert person and avoid coffee-shop sweets, but I do enjoy
Aldo&amp;#8217;s sfogliatelle.  Too much.  &amp;#x2019;Nuff said.&lt;/p&gt;


	&lt;h3&gt; Good web site&lt;/h3&gt;


	&lt;p&gt;One more indicator that Aldo is not your typical Pittsburgh coffee
shop is that they blog.  And, unlike me, &lt;em&gt;they&lt;/em&gt; keep their blog up to
date.  Their blog is a part of their web site at
&lt;a href="http://www.aldocoffee.com/"&gt;www.aldocoffee.com&lt;/a&gt;, which has menus,
Pittsburgh-related tidbits, and even barista profiles, so you can get
to know their staff.  This, too, suggests an unusual attentiveness to
things that other shops overlook.&lt;/p&gt;


	&lt;h3&gt; It all adds up to good stuff&lt;/h3&gt;


	&lt;p&gt;Good espresso, good service, good sandwiches, good
sfogliatelle, and an informative web site featuring an oft-updated
blog.  Clearly, something unusual is happening at Aldo Coffee Co.  I am
not sure how they make it happen, but I am sure about one thing: Aldo is
&lt;em&gt;good stuff&lt;/em&gt;.&lt;/p&gt;


	&lt;p&gt;&lt;strong&gt;Update 2006-03-06:&lt;/strong&gt; I originally wrote that Black Cat was &amp;#8220;darker
than a traditional Northern Italian roast.&amp;#8221; I just got some more Black
Cat, and this batch is lighter than I had remembered.  It looks to
have just brushed up against the second crack.&lt;/p&gt;</description>
      <pubDate>Mon, 30 Jan 2006 21:57:00 -0500</pubDate>
      <guid isPermaLink="false">urn:uuid:1bfd209dd586687e746dd1c6912d0505</guid>
      <author>Tom Moertel</author>
      <link>http://blog.moertel.com/articles/2006/01/30/good-stuff-aldo-coffee-company</link>
      <category>good stuff</category>
      <category>espresso</category>
      <category>pittsburgh</category>
      <trackback:ping>http://blog.moertel.com/articles/trackback/43</trackback:ping>
    </item>
    <item>
      <title>Perl Mongers meet at PAPA-8 pinball tournament kickoff!</title>
      <description>&lt;p&gt;Tonight&amp;#8217;s meeting of the &lt;a href="http://pgh.pm.org/"&gt;Pittsburgh Perl Mongers&lt;/a&gt;
was held at the World Headquarters of the &lt;a href="http://papa.org/"&gt;Professional Amateur
Pinball Association&lt;/a&gt; to coincide with the 
&lt;a href="http://papa.org/papa8/"&gt;&lt;span class="caps"&gt;PAPA 8&lt;/span&gt; World Pinball Championships&lt;/a&gt;.&lt;/p&gt;


	&lt;p&gt;To say it was a cool meeting doesn&amp;#8217;t do it justice. Not only did we
hear a fun talk on writing &lt;span class="caps"&gt;CGI&lt;/span&gt;-contained Mason applications by our own
&lt;a href="http://www.dwright.org/me/me.html"&gt;Dan Wright&lt;/a&gt;, but after the meeting
we wandered around and
&lt;em&gt;played fabulous, well-maintained vintage pinball machines and fine 1980s-era video games&lt;/em&gt;.
And we watched the world&amp;#8217;s best pinball players compete.
And we ate junk food. Yeah!&lt;/p&gt;


	&lt;p&gt;By all means, do look at &lt;a href="http://community.moertel.com/~thor/papa8/"&gt;the &lt;span class="caps"&gt;PAPA 8&lt;/span&gt; photos&lt;/a&gt;. And if you need the love
that only good pinball can provide, experience &lt;span class="caps"&gt;PAPA 8&lt;/span&gt; for yourself:
the tournament runs for the next few days.&lt;/p&gt;


	&lt;p&gt;A big thanks to &lt;span class="caps"&gt;PAPA&lt;/span&gt; for hosting us!&lt;/p&gt;


	&lt;p&gt;&amp;#8212;Tom&lt;/p&gt;</description>
      <pubDate>Fri, 12 Aug 2005 12:00:00 -0400</pubDate>
      <guid isPermaLink="false">urn:uuid:2ece269de30b705a4eafa8541256eca2</guid>
      <author>Tom Moertel</author>
      <link>http://blog.moertel.com/articles/2005/08/12/perl-mongers-meet-at-papa-8-pinball-tournament-kickoff</link>
      <category>perl</category>
      <category>fun stuff</category>
      <category>pittsburgh</category>
      <trackback:ping>http://blog.moertel.com/articles/trackback/48</trackback:ping>
    </item>
  </channel>
</rss>
