0
votes

TL;DR

is their a way I can construct a heterogenous list constrained to some typeclass, without calling a constructor on each element?


I finally have the opportunity to take a deeper dive into Haskell, and I am trying to build a DSL that generates a query string for a SQL like language. The following is pseudo code for what I am trying to attempt, these types will be more refined once I have a better idea of how to approach this. Please excuse the vagueness.

The desired syntax for my dsl is something like

from :: String -> Query
select :: [String] -> Query -> Query
select ["this", "that"] $ from "tbl"

The trouble is that I would also like to allow for things like Column arithmetic and logical operators in the argument to select. For example

(<~) :: String -> Column -> Column -- used for assigning a new name
add  :: String -> String -> Column

select ["stuff" <~ "this" `add` "that", "this"] $ from "tbl"

the output of which could be something like

SELECT 
    this + that as stuff, 
    this
FROM tbl;

The obvious problem is that this requires a heterogeneous list. I could create a new data type to wrap up these values and go on with my life, but I think the result is much more cumbersome

select ["stuff" <~ (Column "this") `add` (Column "that"), Column "this", Column "that"] $ from "tbl"

I have seen some tricks using GADT's and Existentials but they all require wraping each value in a constructor while I stubbornly want my strings as is. Is it possible??

2
Not like that, but you can use Template Haskell to make some custom syntax. Warning: not for the faint of heart. Alternatively, there are some tricks you can play with classes to make heterogeneous lists easier to write (the HList package has such a thing). This will look pretty, but your error messages will be scary.dfeuer

2 Answers

2
votes

GHC has an extension for cases just like this one: OverloadedStrings. The basic idea behind OverloadedStrings is that string literals can be any type which implement the Data.String.IsString typeclass. So for your code, you could do something like this.

import Data.String

newtype Column = Column String

instance IsString Column where
  fromString = Column

And then define your combinators as you were. Now, the bare string literals you want will be interpreted as Columns.

Main*> :t ["those" <~ "this" `and` "that", "thing"]
["those" <~ "this" `and` "that", "thing"] :: [Column]
0
votes

No, proper heterogeneous lists are impossible in Haskell. You will always need some sort of constructor. However, there might be another solution to your problem. You could redefine your combinators to return Strings instead of Columns. Something like this, perhaps.

infixl 5 `add`
add :: String -> String -> String
add a b = a ++ " + " ++ b

infix 2 <~ 
(<~) :: String -> String -> String
new <~ old = old ++ " as " ++ new