В разделе 16.13 Программирование на Haskell с первых принципов тип данных Сворачивать представлен для демонстрации типа, экземпляр Functor которого требует ограничения класса типа для одного из его параметров:
data Wrap f a =
Wrap (f a)
deriving (Eq, Show)
После демонстрации пары некорректных экземпляров Functor for (Wrap f) показан правильный экземпляр:
instance Functor f => Functor (Wrap f) where
fmap g (Wrap fa) = Wrap (fmap g fa)
Тот факт, что этот экземпляр класса типов должен работать, кажется мне правильным. Действительно, GHC принимает это без жалоб.
Чтобы убедить себя, что ограничение «Functor f» необходимо, я попытался создать свой собственный экземпляр класса типов без него. Мой подход фокусируется на сопоставлении с образцом, чтобы использовать f "функторным" способом без fmap, аналогично подходам, показанным ранее в книге. Вот попытка:
instance Functor (Wrap f) where
fmap g (Wrap (f a)) = Wrap f (g a)
Когда я загружаю это в GHCi, я получаю следующую ошибку:
Prelude> :l 16.12-wrap.hs
[1 of 1] Compiling Main ( 16.12-wrap.hs, interpreted )
16.12-wrap.hs:10:17: error: Parse error in pattern: f
|
10 | fmap g (Wrap (f a)) = Wrap f (g a)
| ^^^
Failed, no modules loaded.
Может ли кто-нибудь объяснить проблему с моей попыткой экземпляра? Мне кажется, что у GHC достаточно информации, чтобы сделать вывод, что f имеет вид (* -> *) из определения Wrap вверху, поэтому я не могу понять, почему моя попытка не анализируется.
Другой взгляд на это: в fmap g (Wrap (f a))
f
не является типом (и не конструктором типа).
Не могли бы вы подробнее объяснить, почему f не является конструктором типа? Я думаю, что это источник моих проблем. Неправильно ли говорить, что f должен иметь вид (* -> *) только на основании объявления типа данных? Отсюда я подумал, что (f a) будет допустимым типом для сопоставления с образцом.
@KevinBradner f
в объявлении экземпляра instance Functor f => Functor (Wrap f)
— это конструктор типа. Однако f
в определении fmap
— это не одно и то же и не конструктор типа. Шаблоны соответствуют значениям, а не типам.
@duplode Думаю, я понимаю. Вы говорите, что (fa) нельзя интерпретировать как значение типа 'fa', правильно? Кажется, это имеет смысл, если я представлю конкретный пример, заменяющий [] вместо f.
Когда у вас есть значение типа fa, вы не можете (обязательно) сопоставить его, чтобы извлечь a, в этом весь смысл функторов/монад и т. д., упаковывая значения так, чтобы тип указывал, что они не просто ^ это значение, а скорее, например. действие, результатом которого может быть значение.
Чем f не является, так это не конструктором значений. Кажется, вы смешиваете конструкторы типов и значений. Это часто является источником путаницы из-за широкого использования таких определений, как data D = D Int
. Слева от = вы видите (нулевой) конструктор типа D. Справа — конструктор значения. Чтобы устранить неоднозначность, давайте использовать data D = V Int
. Помните, что мы можем сопоставлять шаблоны только для конструкторов значений (в данном случае V), а не для конструкторов типов (в данном случае D). Вернемся к вашему примеру: f - это переменная типа, обозначающая некоторый (унарный) конструктор типа, но вы не можете сопоставлять его с шаблоном, потому что это не конструктор значений.
fmap g (Wrap (f a))
в левой части определения приводит к ошибке синтаксического анализа, потому что (f a)
не является синтаксически допустимым шаблоном.
My approach focuses on pattern matching to use f in a "functor-ish" way without fmap [...]
Помимо проблемы синтаксиса, вы не можете буквально использовать этот f
в качестве шаблона таким образом. f
в объявлении экземпляра — это конструктор типа, а шаблоны предназначены для сопоставления значений и конструкторов значений. Для минимальной иллюстрации, в...
id :: x -> x
id x = x
... x
из подписи типа, переменной типа, не то же самое x
из левой части определения функции, шаблон, который соответствует значениям (типа x
) и связывает их с переменной (с именем x
). Нет причин, по которым имена должны совпадать.
Если я хорошо понял ваше намерение, план состоял в том, чтобы использовать (f a)
в качестве шаблона, чтобы f
соответствовал любому "функторному" конструктору с чем-то (a
) внутри. Это не работает (мы не можем абстрагироваться от конструкторов таким образом), но даже если бы это как-то работало, этого было бы недостаточно для этой задачи. Это связано с тем, что не все функториальные значения соответствуют форме конструктора, обертывающего какое-либо другое значение. Один пример: Nothing :: Maybe Integer
. Здесь нет ни конструктора унарных значений, ни переносимых значений.
Проблема в
fmap g (Wrap (f a)) =
. В частности,f a
не является допустимым шаблоном. Я не совсем понимаю, что вы пытаетесь там выразить.