<?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: Tag supermarket</title>
    <link>http://blog.moertel.com/articles/tag/supermarket?tag=supermarket</link>
    <language>en-us</language>
    <ttl>40</ttl>
    <description>Quality rants on programming theory and stuff geeks like</description>
    <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>
  </channel>
</rss>
