Concurrent Clean : SQLを組み立てる

CleanでSQLの文法を真似した構文で、SQLを動的に組み立てるようなものを作ってみようと思った。特に、何に使うというわけではないが、唐突に。
10分程度で適当に作ってみた。

Start = sql_select ["col1" @ "t", "col2" @ "s"]
                   ["table1" as "t", "table2" as "s"]
                   ("col1" @ "t" :=: "col3" @ "s" `and` "col2" @ "s" :>: (str "s"))

select t.col1, s.col2 from table1 as t, table2 as s where (t.col1 = s.col3 and s.col2 > 's')

になる。
感想は、SQLは構文に統一感がないので、結構ややこしい気がした。SQLに近づけるということをやめれば、もっとスマートにできそうな気がする。
あと、シンプルな識別子は標準ライブラリに取られているので、関数名や演算子名をつけるのに工夫がいる。(クラスになっているものは、インスタンスを作ればいいのだけど)

module Main

import StdEnv

::Relation :== [Term] -> String
::Term = Term String
::Expr = Expr Relation [Term]
       | ExprAnd [Expr]
       | ExprOr  [Expr]
       | ExprNot Expr
::Table = Table String

sql_select :: [Term] [Table] Expr -> String
sql_select terms tables expr
    = "select " +++ select_clause terms +++
      " from " +++ from_clause tables +++
      " where " +++ where_clause expr
  where
    select_clause [] = abort "no column specified"
    select_clause [Term s] = s
    select_clause [Term s:tr] = s +++ ", " +++ select_clause tr

    from_clause [] = abort "no table specified"
    from_clause [Table s] = s
    from_clause [Table s:tr] = s +++ ", " +++ from_clause tr

    where_clause (Expr f ts) = f ts
    where_clause (ExprAnd [e:er]) = foldl (\x y = "(" +++ x +++ " and " +++ where_clause y +++ ")") (where_clause e) er
    where_clause (ExprOr  [e:er]) = foldl (\x y = "(" +++ x +++ " or " +++ where_clause y +++ ")") (where_clause e) er
    where_clause (ExprNot e) = "not (" +++ where_clause e +++ ")"

(:>:) infixl 7
(:>:) x y = Expr f [x,y]
  where
    f [Term a,Term b] = a +++ " > " +++ b

(:=:) infixl 7
(:=:) x y = Expr f [x,y]
  where
    f [Term a, Term b] = a +++ " = " +++ b

(`and`) infixl 4
(`and`) x y = ExprAnd [x,y]

(@) infixl 8
(@) a b = Term (b +++ "." +++ a)

(as) infixl 8
(as) a b = Table (a +++ " as " +++ b)

str s = Term ("'" +++ s +++ "'")

Start = sql_select ["col1" @ "t", "col2" @ "s"]
                   ["table1" as "t", "table2" as "s"]
                   ("col1" @ "t" :=: "col3" @ "s" `and` "col2" @ "s" :>: (str "s"))