Как интерпретировать сообщения компилятора «Не удалось сопоставить тип: [b0] с: [a2] -> t:»?

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

Если я намеренно дам компилятору неправильное выражение

sum map (+1) [1,2,3]   -- this is wrong on purpose to trigger a
                       -- compiler error

вместо правильного

sum (map (+1) [1,2,3])

тогда я хотел бы понять, что означает вывод компилятора.

Например:

:t sum map (+1) [1,2,3]
<interactive>:1:5: error:
    • Couldn't match type: [b0]
                     with: [a2] -> t
      Expected: (a0 -> b0) -> (a1 -> a1) -> [a2] -> t
        Actual: (a0 -> b0) -> [a0] -> [b0]
    • In the first argument of ‘sum’, namely ‘map’
      In the expression: sum map (+ 1) [1, 2, 3]
f x y z вызывает f с тремя аргументами. Точно так же sum map (+1) [1,2,3] вызывает sum с тремя аргументами. Вам нужны скобки sum (map (+1) [1,2,3]).
chi 31.03.2023 11:00

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

Micha Wiedenmann 31.03.2023 12:23

Большое спасибо за редактирование, стало намного понятнее.

AlexSchell 31.03.2023 12:36
Стоит ли изучать 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 называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
3
92
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

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

Вы хотите применить sum к результату всех остальных, поэтому вам нужны скобки,

sum (map (+1) [1,2,3])

или a $, который сначала оценивает свою правую сторону, прежде чем передать ее левой:

sum $ map (+1) [1,2,3]

Как вы написали, вы применяете sum к map, как вы написали (sum map) (+1) [1,2,3], что не имеет смысла.


Ошибка просто говорит вам, что есть ошибка

    • In the first argument of ‘sum’, namely ‘map’

который (map) имеет тип Actual(a0 -> b0) -> [a0] -> [b0], тогда как единственный способ, которым компилятор мог бы понять все выражение, - это передать вместо map сущность типа Expected(a0 -> b0) -> (a1 -> a1) -> [a2] -> t.

Этот ожидаемый тип является просто следствием дополнительных аргументов, которые вы передали sum map. Действительно, sum map само по себе «хорошо» в том смысле, что в принципе может иметь смысл. Это тип

:: (Foldable ((->) (a -> b)), Num ([a] -> [b])) => [a] -> [b]

где вы уже можете видеть абсурдность ожидания [a] -> [b], который является экземпляром Num.

Но это нормально, потому что типы по-прежнему полиморфны, недостаточно ограничены, чтобы компилятор понял, что это не может работать.

В тот момент, когда вы передаете некоторые типы, которые имеют почти точный тип, например [1,2,3], который является Num a => [a], и (+1), который является Num a => a -> a, тогда компилятор не может понять это и сообщает вам об этом.


Ваш вопрос можно было бы как-то перефразировать

Почему тип x в \x -> sum x (+1) [1,2,3] — это (Foldable t1, Num a1, Num a2, Num ((a1 -> a1) -> [a2] -> t2)) => t1 ((a1 -> a1) -> [a2] -> t2)?

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


Кроме того, посмотрите на этот пример,

sum (:) (+1) [1,2,3]

где я передаю (:) вместо map. Ошибка немного другая:

<interactive>:37:5: error:
    • Couldn't match type: [a0]
                     with: [a2] -> t
      Expected: a0 -> (a1 -> a1) -> [a2] -> t
        Actual: a0 -> [a0] -> [a0]
    • In the first argument of ‘sum’, namely ‘(:)’
      In the expression: sum (:) (+ 1) [1, 2, 3]

Посмотрите, как это соотносится с вашим:

<interactive>:1:5: error:
    • Couldn't match type: [b0]
                     with: [a2] -> t
      Expected: (a0 -> b0) -> (a1 -> a1) -> [a2] -> t
        Actual: (a0 -> b0) -> [a0] -> [b0]
    • In the first argument of ‘sum’, namely ‘map’
      In the expression: sum map (+ 1) [1, 2, 3]

В вашем примере компилятор ожидал (a0 -> b0) -> (a1 -> a1) -> [a2] -> t, тогда как в моем примере он ожидал a0 -> (a1 -> a1) -> [a2] -> t. Значит, компилятор ожидал другого?! Эти типы действительно разные?

(a0 -> b0) -> (a1 -> a1) -> [a2] -> t
a0         -> (a1 -> a1) -> [a2] -> t

Нет, просто первое может принимать первый аргумент типа a0 -> b0, то есть функцию, тогда как второе может принимать любые a0, то есть функции плюс не функции.

И это следствие того факта, что вы передали map, который является менее общим типом, (a -> b) -> [a] -> [b], а я передал (:), который является немного более общим, a -> [a] -> [a], так что первый в конечном итоге ограничил ожидаемую сигнатуру первого аргумента до sum немного больше, то есть (a0 -> b0) вместо a0.

Теперь становится понятнее, но хотелось бы подробнее остановиться на ожидаемом типе: (a0 -> b0) -> (a1 -> a1) -> [a2] -> t Так ли это, что в этом типе выражение (a1 -> a1) соответствует (+1), [a2][1,2,3], t — выходной тип функции суммы? И является ли (a0 -> b0) типом выражения, которое следует использовать вместо карты, чтобы удовлетворить все выражение? Какой может быть пример такой функции, которую мы могли бы использовать здесь в качестве примера?

AlexSchell 31.03.2023 14:53

@AlexScell, нет, тип выражения, которое «следует» использовать вместо map, - это то, что компилятор говорит вам, как ожидаемый тип, (a0 -> b0) -> (a1 -> a1) -> [a2] -> t. Я привел еще один пример, когда компилятор, похоже, ожидает другой тип. На самом деле это не так. Подумайте об этом так: компилятор пытается решить систему алгебраических уравнений, которая не имеет решения. Он пытается это сделать, приходит к противоречию после всех возможных упрощений и говорит вам, что не подходит.

Enlico 01.04.2023 12:39

большое спасибо за пояснения. Но что я до сих пор не могу до конца понять - ожидаемый тип (a0 -> b0) -> (a1 -> a1) -> [a2] -> t все же соответствует всему типу выражения sum map (+1) [1,2,3] ? Поскольку ожидаемый тип оканчивается на t, что соответствует полиморфному скаляру, т. е. результату функции суммирования, а [a2] — это список того, что ожидает сумма. Не так ли выглядит ожидаемый тип? sum ((a0 -> b0) -> (a1 -> a1) -> [a2] -> t) (+1) [1,2,3]. Если это так, то непонятно, откуда берутся промежуточные шаги (a1 -> b1) и другие.

AlexSchell 02.04.2023 14:56

Нет, ошибка ясна, когда вы говорите, что (a0 -> b0) -> (a1 -> a1) -> [a2] -> t является ожидаемым типом для первого аргумента «суммы», а не для всего выражения.

Enlico 02.04.2023 15:04

Спасибо за ваши объяснения и терпение.

AlexSchell 04.04.2023 10:36

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