Seven signs YOU may have created a Gratuitous Domain Specific Language

Posted by Tom Moertel Sat, 18 Aug 2007 17:01:00 GMT

Like chromatic, I have watched the recent irrational exuberance for domain-specific languages (DSLs) with bewilderment. In certain quarters of the programming universe, it seems that creating DSLs is nearly a rite of passage. The problem is, more and more of these DSLs appear to have been created mainly because, well, DSLs are cool these days, even if less “novel” solutions probably would have been more sensible.

Whereas chromatic unhesitatingly confronted the madness head-on, I have so far managed to avoid the fray. Sure, I’ve asked the occasional probing question of the DSL enthusiast, but mostly my reaction has been limited to standing back and staring in mute amazement at the runaway Domain-Specific Fun-Time Language Train, screaming down the tracks, destined for its inevitable high-speed derailment into what I can only expect will be a bridge abutment. But I’m starting to get the feeling that some of the train’s passengers are aboard because they think it’s the Right Thing To Do Train, so maybe it’s time to say something.

To set the record straight, I don’t have anything against DSLs, embedded or otherwise. (I have created my fair share, some of which are actually useful.) No, my concern is limited strictly to the rise of the Gratuitous DSL. So let’s talk about it.

The reason – the right reason – for creating a DSL is because it ultimately lowers the cost of solving problems. If, then, you create a DSL and the cost of solving your problems does not go down, why did you create it? Think about it. Creating a DSL is an expensive proposition. Making people learn your DSL’s syntax, semantics, and underlying domain is a lot to ask – it’s costly. If you do ask, if you do make the imposition, you had better be sure your DSL pays its bills.

But what if your DSL turns out to be a deadbeat? What if using your DSL doesn’t lower the cost of solving problems? Well, guess what? You have created a Gratuitous Domain Specific Language.

Still unsure of whether you’re on the DSL Train for the wrong reason? No problem. Just take this simple, seven-step test:

Seven signs you may have created a Gratuitous Domain Specific Language (GDSL)

  1. You can’t actually explain what a DSL is.
  2. For your DSL, you can’t explain what the domain is.
  3. You have a hard time explaining the DSL’s syntax and semantics.
  4. You have a hard time explaining how the DSL interacts with the language it is embedded in. (For embedded DSLs only.)
  5. A vanilla library API would have captured the domain’s semantics without awkwardness.
  6. It’s easier to express complex domain concepts in general-purpose code than in your DSL.
  7. Your colleagues have a hard time writing things in your DSL.

Did more than a few of the statements ring true? If so, take a bow. You are the proud creator of a Gratuitous DSL!1

Even so, it’s not too late. You can always hop off the DSL Train at the next stop.


1. Rationale for the Seven Signs. Signs 1–4 suggest that your DSL may not even be a DSL. Signs 4–7 suggest that, though your DSL may be real, it may not be paying the bills.

Update: minor edit for clarity.

Update 2008-03-22: edits for clarity.

Posted in ,
Tags , , , , ,
9 comments
no trackbacks
Reddit Delicious

The Supermarket Pricing Kata in Haskell

Posted by Tom Moertel Fri, 28 Apr 2006 20:30:00 GMT

At last night’s meeting of the Pittsburgh Coding Dojo, we worked on the Supermarket Pricing Kata. This particular kata was intended to be food for thought – a “shower kata” – but our goal was to do some coding, so we made the problem more concrete:

  • Come up with a sensible way to represent common supermarket pricing rules such as “buy one, get one free,” “three for a dollar,” ”$0.34 per ounce,” and so on
  • 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’s contents

Most people paired up, but I worked alone because I wasn’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’t like my internal representation of prices because it conflated goods and their pricing rules.

Over lunch today I came up with a more sensible representation and finished off my implementation. Now I’m happy with it.

The code

Here’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 the unstripped source.

{-
   My solution to "The Supermarket Pricing Kata" 
   http://blogs.pragprog.com/cgi-bin/pragdave.cgi/Practices/Kata/KataOne.rdoc

   Tom Moertel <tom@moertel.com>
   2006-04-27
-}

module SupermarketPricing where

import Control.Arrow ((&&&))
import Data.List (groupBy, sort)
import Test.HUnit

type Portion  = Double      
type Count    = Portion     
type Price    = Double      
type Name     = String      

data PricingRule
    = Per Portion Price     
    | For Count Price Price 
  deriving (Eq, Ord, Read, Show)

data Good
    = G { name :: Name, quantity :: !Portion, rule :: PricingRule }
  deriving (Eq, Ord, Read, Show)

per nm y x p  = G nm y (Per x p)     
each nm p     = G nm 1 (For 1 p p)   
for nm n p    = G nm 1 (For n p p)   
bogo          = flip bngo 1          
btgo          = flip bngo 2          
bngo nm n p   = G nm 1 (For n' np p) 
                where (n', np) = (n + 1, p * n)

checkout :: [Good] -> Price
checkout =
    checkoutBy $ sum . map price

subtotal :: [Good] -> [((Portion, Name), Price)]
subtotal =
    checkoutBy $ map ((quantity &&& name) &&& price)

checkoutBy :: ([Good] -> a) -> [Good] -> a
checkoutBy f =
    f . map (foldl1 combine) . groupByName . sort
  where
    groupByName = groupBy (\g1 g2 -> name g1 == name g2)

price :: Good -> Price
price (G nm y (Per x p))    =  y * p / x
price (G nm m (For n p p2)) = (m - r) * p / n + r * p2
  where
    r = fromIntegral $ round m `rem` round n

combine :: Good -> Good -> Good
combine g1@(G nm x rule) g2@(G nm2 x2 rule2)
    | nm /= nm2 || rule /= rule2
    = error $ "can't combine incompatible goods " ++ show [g1, g2]
    | otherwise
    = G nm (x + x2) rule

Read on for an explanation of the code and my unit tests.

Read more...

Posted in , ,
Tags , , , , ,
no comments
no trackbacks
Reddit Delicious