Определение области действия Haskell во вложенных определениях функций с использованием where

#haskell #types #type-signature

#haskell #типы #область видимости #несоответствие типов

Вопрос:

У меня проблема с определением области видимости Haskell в where определениях. Когда у меня есть следующая функция f , где я хочу передать x локально определенной функции f1 без явного использования ее в качестве параметра, я получаю сообщение об ошибке, в котором говорится, что тип x несовместим с типом в выводе f1 , хотя он должен быть таким же:

f :: Eq a => a -> [a]
f x = f1 x
, где
 f1 :: Eq a => a -> [a]
f1 y = [x, y]

Ошибка заключается в следующем:

 Не удалось сопоставить ожидаемый тип 'a1` с предполагаемым типом 'a'
 `a1' - это переменная жесткого типа, связанная
сигнатурой типа для `f1' при тестировании.hs: 4:11
 `a' - это переменная жесткого типа, связанная
сигнатурой типа для `f' при тестировании.hs: 1: 8
 В выражении: x
 В выражении: [x, y]
 В определении `f1': f1 y = [x, y]
Сбой, загруженные модули: нет.

Однако, когда я передаю в x качестве дополнительного параметра, как я сделал в следующем коде с функцией g , она работает нормально:

g :: Eq a => a -> [a]
g x = g1 x x
, где
g1 :: Eq a => a -> a -> [a]
g1 x y = [x, y]

Есть ли способ сделать тип a in f совместимым с типом a (или a1 ) in f1 ?

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

1. Хотя очень хорошей практикой является добавление в ваш код множества сигнатур типов, за исключением чрезвычайно тривиальных частей, я не вижу причин добавлять к ним функции с локальной областью видимости. Очевидно, что такое сигнатура типа, и для подобных тривиальных функций вы теряете больше в удобочитаемости, чем получаете.

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

Ответ №1:

Дэйв прямо выше. Другой способ подумать об этом заключается в том, что, хотя обе ваши подписи типов ссылаются на переменную a , на самом деле это не переменная одного и того же типа. В нотации Haskell-prime обе подписи могут быть более явно записаны как:

forall a . Eq a => a -> [a]

это означает, что для обеих функций они могут принимать аргумент любого типа (в Eq). Очевидно, что здесь это не так. В стандартном Haskell 98 единственным вариантом является отказ от подписи типа для f1 . Но GHC (и другие?) поддержка переменных типа с лексической областью. Итак, вы могли бы написать

 {-# LANGUAGE ScopedTypeVariables #-}

f :: forall a. Eq a => a -> [a]
f x = f1 x
    where
        f1 :: a -> [a]
        f1 y = [ x, y ]
 

и это будет работать нормально.

Ответ №2:

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

f1 :: Eq a => a -> [a]

Несмотря на то, что это локальная функция, вы обобщили эту функцию, чтобы иметь возможность принимать тип, который не будет существовать в f , все, что получает эта функция, ДОЛЖНО поступать из f , поэтому сигнатура типа не нужна.

Просто удалите подпись типа f1.

Редактировать: прочитайте мой пост для себя, это немного неясно. a в f1 — это параметризованный тип, который может принимать что угодно, но переданные ему аргументы уже привязаны к f . Таким образом, эта функция может получать только то, что получает ее родительская функция, сигнатура типа, которую вы ей предоставляете, нарушает это правило. Надеюсь, это немного более понятно.

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

1. Да, это работает, спасибо 🙂 Хотя это все еще вызывает опасения, что невозможно явно ввести f1 функцию (я исхожу из гораздо более строгого языка), но хорошо, пока ничего не ломается 😉

2. Poke: проблема здесь в том, что Haskell более строг к типам, чем вы ожидаете. Ваша исходная подпись типа для ‘f1’ указывала, что аргументом может быть что угодно, принадлежащее классу типов ‘Eq’, но компилятор правильно увидел, что аргумент, переданный в ‘f1’, на самом деле должен был быть именно тем, что было передано в ‘f’. Удаление подписи типа для ‘f1’ не ослабляет строгость типа. Это позволяет компилятору только выводить ввод за вас.