Haskell: не удается создать функцию с двумя плавающими аргументами - PullRequest
6 голосов
/ 03 декабря 2010

Я пытаюсь составить функцию типа (Floating a) => a -> a -> a с функцией типа (Floating a) => a -> a, чтобы получить функцию типа (Floating a) => a -> a -> a. У меня есть следующий код:

test1 :: (Floating a) => a -> a -> a
test1 x y = x

test2 :: (Floating a) => a -> a
test2 x = x

testBoth :: (Floating a) => a -> a -> a
testBoth = test2 . test1
--testBoth x y = test2 (test1 x y)

Однако, когда я компилирую его в GHCI, я получаю следующую ошибку:

/path/test.hs:8:11:
    Could not deduce (Floating (a -> a)) from the context (Floating a)
      arising from a use of `test2'
                   at /path/test.hs:8:11-15
    Possible fix:
      add (Floating (a -> a)) to the context of
        the type signature for `testBoth'
      or add an instance declaration for (Floating (a -> a))
    In the first argument of `(.)', namely `test2'
    In the expression: test2 . test1
    In the definition of `testBoth': testBoth = test2 . test1
Failed, modules loaded: none.

Обратите внимание, что закомментированная версия testBoth компилируется. Странно то, что если я удаляю ограничения (Floating a) из всех сигнатур типов или если я меняю test1, чтобы просто взять x вместо x и y, компиляция testBoth.

Я искал в StackOverflow, на вики-страницах на Haskell, в Google и т. Д. И не нашел ничего об ограничении на составление функций, относящемся к данной конкретной ситуации. Кто-нибудь знает, почему это происходит?

Ответы [ 3 ]

15 голосов
/ 03 декабря 2010
   \x y -> test2 (test1 x y)
== \x y -> test2 ((test1 x) y)
== \x y -> (test2 . (test1 x)) y
== \x -> test2 . (test1 x)
== \x -> (test2 .) (test1 x)
== \x -> ((test2 .) . test1) x
== (test2 .) . test1

Эти две вещи не похожи друг на друга.

   test2 . test1
== \x -> (test2 . test1) x
== \x -> test2 (test1 x)
== \x y -> (test2 (test1 x)) y
== \x y -> test2 (test1 x) y
2 голосов
/ 02 сентября 2015

Ваша проблема не имеет ничего общего с Floating, но с тем фактом, что вы хотите составить функцию с двумя аргументами и функцию с одним аргументом так, чтобы она не проверялась. Я дам вам пример с точки зрения составной функции reverse . foldr (:) [].

reverse . foldr (:) [] имеет тип [a] -> [a] и работает как ожидалось: он возвращает перевернутый список (foldr (:) [] по существу id для списков).

Однако reverse . foldr (:) не проверяет тип. Почему?

Когда типы соответствуют составу функций

Давайте рассмотрим несколько типов:

reverse      :: [a] -> [a]
foldr (:)    :: [a] -> [a] -> [a]
foldr (:) [] :: [a] -> [a]
(.)          :: (b -> c) -> (a -> b) -> a -> c

reverse . foldr (:) [] проверки типов, потому что (.) создает экземпляр для:

(.) :: ([a] -> [a]) -> ([a] -> [a]) -> [a] -> [a]

Другими словами, в аннотации типа для (.):

  • a становится [a]
  • b становится [a]
  • c становится [a]

Итак, reverse . foldr (:) [] имеет тип [a] -> [a].

Когда типы не соответствуют составу функций

reverse . foldr (:) не проверяет тип, потому что:

foldr (:) :: [a] -> [a] -> [a]

Будучи правым оперантом (.), он будет реализовывать тип от a -> b до [a] -> ([a] -> [a]). То есть в:

(b -> c) -> (a -> b) -> a -> c
  • Тип переменной a будет заменен на [a]
  • Переменная типа b будет заменена на [a] -> [a].

Если тип foldr (:) был a -> b, тип (. foldr (:)) будет:

(b -> c) -> a -> c`

(foldr (:) применяется в качестве правого операнда для (.)).

Но поскольку тип foldr (:) равен [a] -> ([a] -> [a]), тип (. foldr (:)) равен:

(([a] -> [a]) -> c) -> [a] -> c

reverse . foldr (:) не проверяет тип, потому что reverse имеет тип [a] -> [a], а не ([a] -> [a]) -> c!

Сова оператор

Когда люди впервые изучают композицию функций в Haskell, они узнают, что, когда у вас есть последний аргумент функции в самой правой части тела функции, вы можете удалить его как из аргументов, так и из тела, заменив или заключив в скобки (или знаки доллара) с точками. Другими словами, следующие 4 определения функции эквивалентны :

f a x xs = g ( h a ( i x   xs))
f a x xs = g $ h a $ i x   xs
f a x xs = g . h a . i x $ xs
f a x    = g . h a . i x

Таким образом, люди получают интуицию, которая говорит: «Я просто удаляю самую правую локальную переменную из тела и аргументов», но эта интуиция ошибочна, потому что, как только вы удалили xs,

f a x = g . h a . i x
f a   = g . h a . i

не эквивалентно ! Вы должны понимать, когда проверки типов композиции функций, а когда нет. Если вышеуказанные 2 были эквивалентны, то это означало бы, что приведенные ниже 2 также эквивалентны:

f a x xs = g . h a . i x $ xs
f a x xs = g . h a . i $ x xs

, что не имеет смысла, потому что x не является функцией с xs в качестве параметра. x является параметром для функции i, а xs является параметром для функции (i x).

Есть способ сделать функцию с 2 параметрами бессмысленными. А это значит использовать оператор «сова»:

f a x xs = g . h a .  i x xs
f a      = g . h a .: i
  where (.:) = (.).(.)

Приведенные выше два определения функций эквивалентны. Читайте подробнее об операторе «Сова» .

Ссылки

Программирование на Haskell становится намного проще и понятнее, когда вы понимаете функции, типы, частичное применение и каррирование, составление функций и оператор доллара. Чтобы понять эти понятия, прочитайте следующие ответы StackOverflow:

Читайте также:

2 голосов
/ 03 декабря 2010

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

test1 :: Int -> Char -> Int
test1 = undefined

test2 :: Int -> Int
test2 x = undefined

testBoth = test2 . test1

Какой тип testBoth?Ну, мы берем тип (.) :: (b -> c) -> (a -> b) -> a -> c и поворачиваем рукоятку, чтобы получить:

  1. b ~ Int (аргумент test2 объединен с первым аргументом (.))
  2. c ~ Int (результат test2, объединенный с результатом первого аргумента (.))
  3. a ~ Int (test1 аргумент 1, объединенный с аргументом 2 (.))
  4. b ~ Char -> Int (результат test1, объединенный с аргументом 2 (.))

, но подождите!эта переменная типа 'b' (# 4, Char -> Int) должна объединяться с типом аргумента test2 (# 1, Int).О нет!

Как это сделать?Правильное решение:

testBoth x = test2 . test1 x

Существуют и другие способы, но я считаю, что это наиболее читабельно.

Редактировать: Так что за ошибка пыталась сказать вам?Говорилось, что объединение Floating a => a -> a с Floating b => b требует instance Floating (a -> a) ... хотя это правда, вы действительно не хотели, чтобы GHC пытался рассматривать функцию как число с плавающей запятой.

...