Search code examples
haskelltypesdslgadt

How to resolve ambiguity in my GADTs


I have two GADTs I am using to model a SQL EDSL. To keep the client facing api clean and simple I want to use OverloadedStrings to cast string literals to a Column Selection.

Therefore you can simply type

select ["a", "b"] $ from tbl

instead of

select [Column "a", Column "b"] $ from tbl

The problem is that select allows for both Column Selections and Reductions to allow for queries that perform aggregations.

mean :: Column Selection -> Column Reduction

select :: [Column a] -> Query b -> Query Selection
select [mean "a"] $ from tbl

and thus the strings are ambiguous in this context of [Column a]. But select [mean "a"] $ from tbl is valid since mean provides the necessary context to infer that the string literal is a Column Selection.

Can anyone recommend a way out of this mess?

My current code below (irrelevant instances omitted)

{-# LANGUAGE 
    GADTs
  , RankNTypes
  , DataKinds
  , TypeFamilies
  , FlexibleContexts
  , FlexibleInstances
  , OverloadedStrings #-}

data Sz = Selection | Reduction deriving Show
data Schema = Schema{name :: Maybe String, spec :: [Column Selection]} 

type family ColOp (a :: Sz) (b :: Sz) where
    ColOp Selection Selection = Selection
    ColOp Selection Reduction = Selection
    ColOp Reduction Selection = Selection
    ColOp Reduction Reduction = Reduction

data Column (a :: Sz) where
    Column  :: String -> Column Selection
    Assign  :: String -> Column a -> Column a
    FKey    :: String -> Schema -> Column Selection
    BinExpr :: BinOp  -> Column a -> Column b -> Column (ColOp a b)
    LogExpr :: LogOp  -> Column a -> Column b -> Column Selection
    AggExpr :: AggOp  -> Column Selection -> Column Reduction

instance IsString (Column Selection) where
    fromString s = Column s

data Query (a :: Sz) where
    Table  :: Schema -> Query Selection
    Select :: [Column a] -> Query b -> Query Selection
    Update :: [Column a] -> Query b -> Query Selection
    Where  :: [Column Selection] -> Query Selection -> Query Selection
    Group  :: [Column Selection] -> Query Selection -> Query Reduction

I would also like to make the following signature fail for Select/Update:

[Column Selection] -> Query Reduction -> Query Selection

But that's a whole other can of worms...


Solution

  • The compiler is correct to give you an ambiguous type error for Select ["a"] - the IsString (Column Selection) instance can be chosen only if a priori the argument to Column is known to be Selection. This is exactly the intended behaviour.

    What you want is the following:

    instance (x ~ Selection) => IsString (Column x) where
        fromString = Column 
    

    This will allow the compiler to infer that "x" :: Column _ must actually be "x" :: Column Selection, as opposed to requiring it.

    Select [mean "a"] is a completely different situation - since mean :: Column Selection -> Column Reduction, the compiler knows, before instance selection happens, that "a" :: Column Selection, because the type of mean forces this to be the case.