Haskell: не удается составить функцию с двумя плавающими аргументами

Я пытаюсь составить функцию типа (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 и т. Д. И не нашел ничего об ограничении на композицию функций, относящемся к этой конкретной ситуации. Кто-нибудь знает, почему это происходит?


person gdj    schedule 02.12.2010    source источник


Ответы (3)


   \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
person ephemient    schedule 02.12.2010
comment
Спасибо. Понятно и лаконично. Я раньше не видел обозначение (test2.), Но оно должно означать f | - ›test2.f. - person gdj; 03.12.2010
comment
@gdj: в Haskell двоичный оператор может частично применяться для формирования раздел. - person ephemient; 03.12.2010
comment
Да, я в курсе; просто никогда не видел, чтобы он использовался. но это имеет смысл. Спасибо! - person gdj; 03.12.2010

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

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

test2 :: Int -> Int
test2 x = undefined

testBoth = test2 . test1

Какой тип теста? Что ж, берем тип (.) :: (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 пытался рассматривать функцию как число с плавающей запятой.

person Thomas M. DuBuisson    schedule 02.12.2010
comment
Спасибо, это интуитивно понятное объяснение. Когда я пропустил класс типов, это должно было означать, что testBoth возьмет x и передаст его test1, а затем возьмет получившуюся функцию f: y | - ›x и передаст ее test2, которая затем вернет f. Это явно не то, что я хотел. - person gdj; 03.12.2010

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

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

Однако 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:

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

person Mirzhan Irkegulov    schedule 02.09.2015