Неоднозначный тип для полиморфных функциональных членов драйвера GADT

Я тестирую код для небольшого эксперимента, который я провожу, но в самом начале я натолкнулся на препятствие, которое я не знаю, как исправить.

data DatabaseDriver a b where
  DatabaseDriver :: (Table table, Field field) =>  {
      dbInsert :: table -> [field] -> String
    , dbSelect :: table -> [field] -> String
    } -> DatabaseDriver a b

class Table a where
    tableName :: a -> String

class Field a where
    fieldName :: a -> String
    fieldValue :: a -> String

psqlDriver = DatabaseDriver insert select
    where
        insert t fs = "insert into " ++ tableName t ++ " (" ++ fieldNames fs ++ ") values (" ++ fieldValues fs ++ ")"
        select t fs = "select " ++ fieldNames fs ++ " from " ++ tableName t
        fieldNames = joinComma fieldName
        fieldValues = joinComma fieldValue
        joinComma f = foldl (\a n -> a ++ ", " ++ n) "" . map f

Хорошо, это тестовый код, функции драйвера будут намного сложнее, чем этот, но даже в этом тесте я получаю ошибку «Неоднозначная переменная типа 'a0' в ограничении: (Поле a0), возникающее из-за использования` fieldName '. Итак, компилятор видит, что fieldName применяется к полю, но, очевидно, ему нужен более конкретный тип. Я думаю, что сохранение полиморфных функций делает pgsqlDriver не конкретным классом?

Но идея была бы в том, что эти функции полиморфны. По этой причине я решил использовать здесь GADT, чтобы я мог наложить ограничения типа на параметры этих функций драйвера, не повторяя их в каждом экземпляре. Планируется, что определенный драйвер базы данных сможет работать с любыми экземплярами полей и таблиц. Может ли это просто не быть сделано, и мой тип DatabaseDriver также должен быть классом типа?


person Jason    schedule 09.04.2013    source источник


Ответы (1)


Здесь есть три варианта. Первый вариант - добавить

{-# LANGUAGE NoMonomorphismRestriction #-}

в начало файла вашего модуля.

Второй вариант - добавить в psqlDriver явную подпись типа.

psqlDriver :: (Field field, Table table) => (table -> [field] -> String) -> DatabaseDriver a b

Причина всего этого имеет несколько нюансов, и более подробную информацию можно найти здесь

Третий вариант - изменить определение psqlDriver на

psqlDriver = DatabaseDriver insert select

Однако это действительно неоднозначно - нет причин предпочитать какой-либо конкретный экземпляр Table или Field другому. Возможно, вы хотите определить DatabaseDriver следующим образом.

data DatabaseDriver table field where
  DatabaseDriver :: (Table table, Field field) =>  {
      dbInsert :: table -> [field] -> String
    , dbSelect :: table -> [field] -> String
    } -> DatabaseDriver table field

Если исходное определение DatabaseDriver будет переписано как ADT, станет более очевидным, почему.

Как в настоящее время написано в вопросе, перевод на ADT

{-# LANGUAGE ExistentialQuantification #-}

data DatabaseDriver a b
  = forall table field .
    (Table table, Field field) => DatabaseDriver
    { dbInsert :: table -> [field] -> String
    , dbSelect :: table -> [field] -> String
    }

Обратите внимание на вложенный forall table field и на то, что table и field не имеют отношения к a или b.

Предполагаемый перевод либо

data DatabaseDriver table field
  = (Table table, Field field) => DatabaseDriver
    { dbInsert :: table -> [field] -> String
    , dbSelect :: table -> [field] -> String
    }

или скорее всего

data DatabaseDriver table field
  = DatabaseDriver
    { dbInsert :: table -> [field] -> String
    , dbSelect :: table -> [field] -> String
    }

Наличие ограничений класса типа в определении DatabaseDriver не позволяет вам удалить ограничения класса типа при любом использовании DatabaseDriver, в частности psqlDriver. В обоих приведенных выше переводах ADT тип psqlDriver

psqlDriver :: (Table table, Field field) => DatabaseDriver table field
person ScootyPuff    schedule 09.04.2013
comment
Мне кажется, что ни один из них не работает. Прагма Нет моно ограничений действительно устраняет некоторые ошибки, но по-прежнему показывает ошибку в определении, а добавленная подпись не очищает это. Но я заметил, что вы даете подпись функции, в то время как psqlDriver на самом деле является просто записью. Я подозреваю, что записи не могут иметь полиморфные функции, потому что это сделало бы их полиморфными, что не имеет смысла (я полагаю?) Для типа данных. - person Jason; 09.04.2013
comment
Как написано сейчас, psqlDriver - это не запись, а функция. По-прежнему требуется второй аргумент dbSelect. Записи определенно могут быть полиморфными. Если вы хотите, чтобы psqlDriver имел тип forall a b . DatabaseDriver a b, вам нужно будет указать второй аргумент, возможно, с именем select (хотя это имя не имеет значения, и я выбираю это имя только из-за текущего имени для первого аргумента insert). - person ScootyPuff; 09.04.2013
comment
Теперь я понимаю, что было задумано, и изменил ответ. - person ScootyPuff; 09.04.2013
comment
О боже, на самом деле я хотел определить select (обратите внимание, что функция определена в предложении where, просто не используется в операторе верхнего уровня), и это в моем коде. Извините за путаницу. - person Jason; 09.04.2013
comment
Если цель параметров типа a и b в DatabaseDriver a b состоит в том, чтобы разрешить любой экземпляр Table и любой экземпляр Field, то третий вариант должен быть вашим решением. Далее я отредактирую третий вариант, чтобы объяснить, почему. - person ScootyPuff; 09.04.2013