Функциональные зависимости / семейства типов

#haskell #types

#haskell #типы

Вопрос:

С помощью функциональных зависимостей я могу ограничить тип зависимого параметра в классе типов, используя многопараметрические классы типов. Точно так же:

 {-# LANGUAGE FunctionalDependencies, MultiParamTypeClasses,TypeSynonymInstances  #-}

class (Num a, Integral b) => F a b | a -> b where
  f :: a -> b

instance F Int Int where
  f = id

instance F Float Integer where
  f = truncate
  

И все будет работать идеально.

 > f (1 :: Int)
1
> f (1.9 :: Float)
1
  

Но если я попытаюсь написать что-то вроде

 instance F Double String where
  f = show
  

Я получу следующую ошибку компиляции:

 No instance for (Integral String)
arising from the superclasses of an instance declaration
Possible fix: add an instance declaration for (Integral String)
In the instance declaration for `F Double String'
  

Есть ли способ подойти к этому с семействами типов вместо fundeps?

Ответ №1:

В принципе, нет, и на самом деле это не имеет ничего общего с функциональными зависимостями (или семействами типов). Ваше определение класса имеет

 class (Num a, Integral b) => F a b
  

который объявляет, что должен существовать встроенный экземпляр для b . У String A нет экземпляра Integral, поэтому у вас не может быть ничего подобного форме, F a String если вы не определите

 -- String is a type synonym for [Char]
instance Integral [Char] where
  

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

Комментарии:

1. Это был просто пример. Я не собираюсь делать строки целыми или наоборот, я хочу ограничить тип b (параметр, который зависит от a ) целым в определении класса!

2. Но вы не можете хотеть ограничивать это и в то же время хотеть экземпляр F, где это ограничение не выполняется! Что должен делать плохой компилятор? Сделать исключение, потому что это вы?

3. Я не хочу иметь экземпляр F, где ограничение не выполняется, это просто пример ошибки, которую должен выдать компилятор. В этом нет ничего плохого! И мне интересно, мог бы я переписать этот код (который завершается ошибкой в F Double String экземпляре, но работает с instance F Int Int ), используя семейства типов.

4. Я интерпретировал ваш вопрос как «Это не компилируется, могу ли я заставить его компилироваться с помощью AT вместо FunDeps?». Часть со строками / ошибкой компилятора совершенно не имеет значения, вероятно, было бы более понятно, если бы вы убрали ее. Поскольку вы включили ограничение суперкласса в версию fundep, ясно, что любое решение AT также должно было бы учитывать их.

Ответ №2:

Я думаю, вы хотите иметь что-то вроде этого:

 {-# LANGUAGE TypeFamilies, FlexibleContexts  #-}
class Num a => G a where
  type B a
  g :: (Integral (B a)) => a -> B a

instance G Int where
  type B Int = Int
  g = id

instance G Float where
  type B Float = Integer
  g = truncate
  

В этом примере напрямую используется не семейство типов, а связанный синоним типа, который активируется тем же флагом. Идея довольно проста. Вместо явного указания второго параметра для класса типов мы определяем синоним типа, который заполняется соответствующим типом.

Я также впервые использую синонимы связанных типов, но, похоже, это довольно интересная функция.

Комментарии:

1. Спасибо за объяснение. Это именно то, что я имел в виду.