Указание сигнатуры типа функции в предложении where
Следующая функция реализует старую старую функцию фильтрации из списков с помощью библиотеки рекурсивных схем.
import Data.Functor.Foldable
catafilter :: (a -> Bool) -> [a] -> [a]
catafilter p = cata alg
where
-- alg :: ListF a [a] -> [a]
alg Nil = []
alg (Cons x xs) = if (p x) then x : xs else xs
Компилируется и короткий тест как catafilter odd [1,2,3,4]
успешно. Однако, если я раскомментирую подпись типа для alg
Я получаю следующую ошибку:
src/cata.hs:8:30: error:
• Couldn't match expected type ‘a’ with actual type ‘a1’
‘a1’ is a rigid type variable bound by
the type signature for:
alg :: forall a1. ListF a1 [a1] -> [a1]
at src/cata.hs:6:5-29
‘a’ is a rigid type variable bound by
the type signature for:
catafilter :: forall a. (a -> Bool) -> [a] -> [a]
at src/cata.hs:3:1-39
• In the first argument of ‘p’, namely ‘x’
In the expression: (p x)
In the expression: if (p x) then x : xs else xs
• Relevant bindings include
xs :: [a1] (bound at src/cata.hs:8:18)
x :: a1 (bound at src/cata.hs:8:16)
alg :: ListF a1 [a1] -> [a1] (bound at src/cata.hs:7:5)
p :: a -> Bool (bound at src/cata.hs:4:12)
catafilter :: (a -> Bool) -> [a] -> [a] (bound at src/cata.hs:4:1)
|
8 | alg (Cons x xs) = if (p x) then x : xs else xs
| ^
В ответе на вопрос SO- type-signature-in-a-where- as предлагается использовать расширение ScopedTypeVariables. Комментарий в последнем ответе на вопрос " почему это так необычно для использования типа подписи типа в где" предлагает использовать полное количественное определение.
И я добавил:
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE RankNTypes #-}
в верхней части моего модуля и пробовал разные типы подписей для ALG, как: alg :: forall a. ListF a [a] -> [a]
или же alg :: forall b. ListF b [b] -> [b]
или добавление поля для подписи типа катализатора. Ничего не скомпилировано!
Вопрос: Почему я не могу указать сигнатуру типа для alg?
3 ответа
Без расширений оригинальный некомментированный код
catafilter :: (a -> Bool) -> [a] -> [a]
catafilter p = cata alg
where
alg :: ListF a [a] -> [a]
alg Nil = []
alg (Cons x xs) = if (p x) then x : xs else xs
эквивалентно, после включения ScopedTypeVariables
для явного количественного определения всех переменных типа следующим образом:
catafilter :: forall a. (a -> Bool) -> [a] -> [a]
catafilter p = cata alg
where
alg :: forall a. ListF a [a] -> [a]
alg Nil = []
alg (Cons x xs) = if (p x) then x : xs else xs
Это в свою очередь эквивалентно (путем альфа-преобразования количественных переменных)
catafilter :: forall a. (a -> Bool) -> [a] -> [a]
catafilter p = cata alg
where
alg :: forall b. ListF b [b] -> [b]
alg Nil = []
alg (Cons x xs) = if (p x) then x : xs else xs
и это вызывает ошибку типа, так как p
хочет a
аргумент, но p x
проходит b
аргумент.
Дело в том, что с включенным расширением функция начинается с forall b. ...
обещает, что может работать с любым выбором b
, Это обещание слишком сильно для alg
, который работает только на том же a
из catafilter
,
Итак, решение заключается в следующем. Тип catafilter
могу пообещать работать с любым a
вызывающий абонент может выбрать: мы можем добавить forall a.
там. Вместо, alg
должен обещать только работать с тем же a
из catafilter
, поэтому мы повторно используем переменную типа a
без добавления другого forall
,
catafilter :: forall a. (a -> Bool) -> [a] -> [a]
catafilter p = cata alg
where
alg :: ListF a [a] -> [a]
alg Nil = []
alg (Cons x xs) = if (p x) then x : xs else xs
это компилируется с ScopedTypeVariables
видит это a
находится в области видимости и не добавляет неявный forall
в alg
(как это было бы без расширения).
Резюме:
- Без
ScopedTypeVariables
каждая аннотация типа имеет свою собственную неявнуюforall ...
сверху, количественная оценка всех переменных. Никакая аннотация не может ссылаться на переменные других аннотаций (вы можете использовать одно и то же имя, но это не считается той же самой переменной). - С
ScopedTypeVariables
, Определениеfoo :: forall t. T t u ; foo = def
обрабатывается следующим образом:- переменная типа
t
повсеместно определяется количественно и вводится в объем при проверке типаdef
: введите аннотации вdef
может относиться кt
- переменная типа
u
если в настоящее время в области, относится к внешне определеннымu
- переменная типа
u
, если не в области видимости, универсально определяется количественно, но не вводится в область при проверке типаdef
(для совместимости здесь мы следуем тому же поведению без расширения)
- переменная типа
Это работает
{-# Language ScopedTypeVariables #-}
import Data.Functor.Foldable
catafilter :: forall a. (a -> Bool) -> [a] -> [a]
catafilter p = cata alg
where
alg :: ListF a [a] -> [a]
alg Nil = []
alg (Cons x xs) = if (p x) then x : xs else xs
Если вы опустите forall
это совершенно разные a
с (хотя синтаксически они одинаковы).
Из-за неявного количественного определения, ваша некомментированная версия считается
catafilter :: forall a. (a -> Bool) -> [a] -> [a]
catafilter p = cata alg
where
alg :: forall a1. ListF a1 [a1] -> [a1]
alg Nil = []
alg (Cons x xs) = if (p x) then x : xs else xs
Это объясняет ваше сообщение об ошибке:
Couldn't match expected type ‘a’ with actual type ‘a1’
Предикат (p :: a -> Bool
) ожидает аргумент типа a
но это было дано x :: a1
от Cons x xs :: ListF a1 [a1]
!
Посмотрите, имеет ли смысл явно выраженная версия, учитывая привязки из сообщения об ошибке:
xs :: [a1]
x :: a1
alg :: ListF a1 [a1] -> [a1]
p :: a -> Bool
catafilter :: (a -> Bool) -> [a] -> [a]
Это работает, и позволяет избежать всего этого нелогичного мошенничества с forall
s
{-# LANGUAGE ScopedTypeVariables #-}
import Data.Functor.Foldable
catafilter :: (a -> Bool) -> [a] -> [a]
catafilter p = cata alg
where
-- alg :: ListF a [a] -> [a]
alg (Nil :: ListF aa [aa]) = [] :: [aa]
alg (Cons x xs) = if (p x) then x : xs else xs
На alg Nil
Уравнение я мог бы на самом деле использовать Tyvar a
: Я использовал aa
просто чтобы показать, что они отличные привязки. Так как aa
появляется на шаблоне, компилятор объединяет его с a
от подписи за catafilter
,
Вы также можете / вместо этого поместить аннотации типа на alg Cons
уравнение.
Я понимаю путаницу @Jogger, почему GHC так суетливы относительно позиции forall
; и нервозность, что forall
возможно указывает RankNTypes
,
Проблема в том, что alg
зависит от внешнего p
, так alg
Тип не может быть просто полиморфным.
Один простой способ обойти это - сделать его независимым от каких-либо внешних сущностей, передав их в качестве аргументов функции, чтобы функция могла иметь свой просто полиморфный тип, как и ожидалось:
catafilter :: (a -> Bool) -> [a] -> [a]
catafilter = cata . alg
where
alg :: (b -> Bool) -> ListF b [b] -> [b]
alg p Nil = []
alg p (Cons x xs) = if (p x) then x : xs else xs
Для этого не нужны языковые расширения.