Как повторно использовать переменную типа во внутренней декларации типа

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

foo :: (a -> a) -> a -> a
foo f arg = bar arg
  where
    bar :: a -> a
    bar a = f a

дает эту ошибку:

src\Test.hs:7:14:
    Couldn't match expected type `a' against inferred type `a1'
      `a' is a rigid type variable bound by
          the type signature for `foo' at src\Test.hs:3:8
      `a1' is a rigid type variable bound by
           the type signature for `bar' at src\Test.hs:6:11
    In the first argument of `f', namely `a'
    In the expression: f a
    In the definition of `bar': bar a = f a

Как я могу выразить, что первый аргумент bar должен быть того же типа, что и второй аргумент foo, так что я могу применить f к нему?

Спасибо.

Ответ 1

Я думаю, вы можете сделать это в целом с помощью ScopedTypeVariables, который поддерживает GHC. Это, безусловно, компилируется:

{-# LANGUAGE ScopedTypeVariables #-}
foo :: forall a. (a -> a) -> a -> a
foo f arg = bar arg
  where
    bar :: a -> a
    bar a = f a

Обратите внимание на "forall a."

Ответ 2

Существует еще одно обходное решение. Вместо ссылки f внутри внутренней функции bar, продолжите bar, чтобы принять f в качестве первого аргумента и использовать частичное приложение в родительском.

foo :: (a -> a) -> a -> a
foo f arg = (bar f) arg
  where
    bar :: (a -> a) -> a -> a
    bar f a = f a

В качестве других ответов не требуется ScopedTypeVariables или явный код проверки типов.

Объяснение

Для ясности измените параметр типа в bar на b, а также переименуйте его аргумент.

foo :: (a -> a) -> a -> a
foo f arg = bar arg
  where
    bar :: b -> b
    bar x = f x

Haskell жалуется, потому что bar аннотируется как b -> b (для любого произвольного типа b), но f x пытается применить аргумент типа b к функции типа a -> a (для конкретный, связанный a). Другими словами, внутренняя функция не такая общая, как рекламирует ее аннотация.

Передача f в bar означает, что для выражения (bar f) переменная типа b привязана к тому же типу, что и a.

Еще проще

И, наконец, без изменения чего-либо еще, если вы хотите опустить подпись типа для внутренней функции bar, Haskell выведет свой тип точно так, как вы хотите. То есть, поскольку bar применяется f из родительской функции foo, тип bar будет повторно использовать параметр типа a из типа foo.

foo :: (a -> a) -> a -> a
foo f arg = bar arg
  where
    -- Type: bar :: a -> a
    bar a = f a

Ответ 3

Этот ответ на другой вопрос показывает трюк для использования, если вы не хотите использовать расширение ScopedTypeVariables.