Почему GHC не анализирует экземпляр функтора для «переноса данных f a = Wrap (f a)»?

В разделе 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 a не является допустимым шаблоном. Я не совсем понимаю, что вы пытаетесь там выразить.

dfeuer 09.04.2019 04:05

Другой взгляд на это: в fmap g (Wrap (f a))f не является типом (и не конструктором типа).

duplode 09.04.2019 04:06

Не могли бы вы подробнее объяснить, почему f не является конструктором типа? Я думаю, что это источник моих проблем. Неправильно ли говорить, что f должен иметь вид (* -> *) только на основании объявления типа данных? Отсюда я подумал, что (f a) будет допустимым типом для сопоставления с образцом.

Kevin Bradner 09.04.2019 04:11

@KevinBradner f в объявлении экземпляра instance Functor f => Functor (Wrap f) — это конструктор типа. Однако f в определении fmap — это не одно и то же и не конструктор типа. Шаблоны соответствуют значениям, а не типам.

duplode 09.04.2019 04:16

@duplode Думаю, я понимаю. Вы говорите, что (fa) нельзя интерпретировать как значение типа 'fa', правильно? Кажется, это имеет смысл, если я представлю конкретный пример, заменяющий [] вместо f.

Kevin Bradner 09.04.2019 04:31

Когда у вас есть значение типа fa, вы не можете (обязательно) сопоставить его, чтобы извлечь a, в этом весь смысл функторов/монад и т. д., упаковывая значения так, чтобы тип указывал, что они не просто ^ это значение, а скорее, например. действие, результатом которого может быть значение.

moonGoose 09.04.2019 04:39

Чем f не является, так это не конструктором значений. Кажется, вы смешиваете конструкторы типов и значений. Это часто является источником путаницы из-за широкого использования таких определений, как data D = D Int. Слева от = вы видите (нулевой) конструктор типа D. Справа — конструктор значения. Чтобы устранить неоднозначность, давайте использовать data D = V Int. Помните, что мы можем сопоставлять шаблоны только для конструкторов значений (в данном случае V), а не для конструкторов типов (в данном случае D). Вернемся к вашему примеру: f - это переменная типа, обозначающая некоторый (унарный) конструктор типа, но вы не можете сопоставлять его с шаблоном, потому что это не конструктор значений.

Artem Pelenitsyn 09.04.2019 04:46

Связано: Написание универсального экземпляра функтора для конструкторов типов?

duplode 09.04.2019 05:13
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
8
107
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

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. Здесь нет ни конструктора унарных значений, ни переносимых значений.

Другие вопросы по теме