Что есть ((+). (+)) В Haskell?
В ghci,
:t ((+).(+))
> ((+).(+)) :: (Num (a -> a), Num a) => a -> (a -> a) -> a -> a
но что это? Может ли кто-нибудь дать мне пример использования этого, пожалуйста?
Как можно использовать одни составные 2 функции, которые принимают по 2 параметра?
например, как работает (map.map) :: (a -> b) -> [[a]] -> [[b]]
?
(^.^)
(-.-)
(+.+)
(не может не сделать смешные лица из него. PS: Я думал, что это означает сказать компилятору, как вы себя чувствуете сегодня)
Ответы
Ответ 1
Num (a -> a)
(или, например, Eq (a -> a)
) в основном является индикатором кода, который не имеет никакого смысла 1 но, тем не менее, компилятор выводит сигнатуру типа (бессмысленного) типа. Обычно это появляется, когда вы забыли применить функцию к некоторому аргументу. В этом случае, очевидно, (+)
требуется аргумент "простого номера", чтобы стать "простой функцией", к которой вы можете написать еще одну такую функцию.
Тем не менее, (a -> a)
- это действительно допустимый тип функций, которые вы также можете передать, а не как числа. Например, map . (+)
является отличной комбинацией:
Prelude> :t map . (+)
map . (+) :: Num b => b -> [b] -> [b]
Prelude> zipWith (map . (+)) [10,20,30] [[1,2],[3,4]]
[[11,12],[23,24]]
потому что map
на самом деле ожидает функцию в качестве своего первого аргумента. Аналогично,
Prelude> zipWith (map . map) [(+10),(+20),(+30)] [[[1,2],[3,4]],[[5,6]]]
[[[11,12],[13,14]],[[25,26]]]
Здесь правый map
принимает простую функцию (например, числовое значение) и возвращает соответствующую функцию списка. Затем эта функция подается влево map
, что приводит к функции, которая отображает вложенные списки.
1 Собственно, вы можете заставить его иметь смысл, указав
instance (Num a) => Num (b -> a) where
fromInteger x = const $ fromInteger x
f + g = \x -> f x + g x
Лично я не поклонник этого. Это сбивает с толку, например let a = 3 in 4 a
создает 4
, когда большинство людей ожидают умножения на 12
.
Ответ 2
Это не сработает. Как говорит ghci, для использования этой функции у вас должен быть экземпляр Num (a -> a)
, но a -> a
, очевидно, не является числом.
Это связано с тем, что (+)
предполагает получить два числовых параметра, но с композицией, которую вы написали, вместо этого вы указали частично примененную функцию, a -> a
, упомянутую в сигнатуре вычисленного типа.
Обычно при составлении функций, которые принимают более одного параметра, вы частично применяете их сначала, чтобы свести их к функциям, которые принимают только один параметр, например. (+1) . (*2)
, примененный к 3
, приведет к (3 * 2) + 1 = 7
Ответ 3
f . f
может иметь смысл для двоичной функции f
; он полностью зависит от сигнатуры f
. Ключ состоит в том, что частичное применение внутреннего f
к его первому аргументу должно дать что-то, что является допустимым вводом внешнего f
.
Например, при map :: (a -> b) -> [a] -> [b]
мы можем объединить map . map
:
map :: (a -> b) -> [a] -> [b]
map :: (c -> d) -> [c] -> [d]
. :: (e -> f) -> (f -> g) -> (e -> g)
e === a -> b
f === [a] -> [b]
=== c -> d
c === [a]
d === [b]
g === [c] -> [d] === [[a]] -> [[b]]
map . map :: e -> g
:: (a -> b) -> [[a]] -> [[b]]
Итак, как и ожидалось, map . map
принимает преобразование a -> b
и дает нам преобразование из списка-of-list-of-a
в список-of-list-of-b
. Мы можем проверить это с помощью ручного приложения (map . map) f ll
:
(map . map) f ll
= map (map f) ll
= map (\l -> map f l) ll
Но если мы попробуем то же самое с (+) :: Num a => a -> a -> a
, все идет ужасно неправильно:
(+) :: Num a => a -> a -> a
(+) :: Num b => b -> b -> b
. :: (c -> d) -> (d -> e) -> (c -> e)
c === a
d === a -> a
=== b
e === b -> b === (a -> a) -> (a -> a)
(+) . (+) :: c -> e
:: (Num a, Num (a -> a)) => a -> (a -> a) -> (a -> a)
Итак, частичное применение внутреннего +
дает преобразование a -> a
, внешний +
затем пытается добавить это преобразование к другой функции, которую мы ожидаем предоставить. Поскольку нет смысла добавлять преобразования, общий (+) . (+)
тоже не имеет смысла.
Ответ 4
g . f
означает сначала применить f
, а затем применить g
к результату f
, в
другими словами, его можно переписать как
\x -> g (f x)
Таким образом,
((+) . (+))
можно переписать как
\x -> (\y -> (x +) + y)
В соответствии с типом (+)
в приведенной выше лямбда-абстракции x
требуется
имеющего тип Num a => a
, y
, имеющий тип Num a => Num (a -> a)
, как предполагалось
ghci
(Num a, Num (a -> a)) => a -> (a -> a) -> a -> a
Итак, если мы сделали a -> a
экземпляр типа class Num a
, например,
вот один из способов добиться этого.
{-# LANGUAGE FlexibleInstances #-}
instance (Num a) => Num ((->) a a) where
a + b = \x -> a x + b x
a * b = \x -> a x * b x
a - b = \x -> a x - b x
negate a = \x -> negate $ a x
abs a = \x -> abs $ a x
signum a = \x -> signum $ a x
fromInteger n = \_x -> fromInteger n
мы можем использовать ((+) . (+))
как это
*Main> ((+) . (+)) 1 (+2) 3
9
Потому что ((+) . (+))
равно
\x -> \y -> (x +) + y
что означает ((+) . (+)) 1 (+2) 3
равно
((1 + ) + (+ 2)) 3
в соответствии с определением (+)
в случае (a -> a)
, ((1+) +
(+2))
равно
\x -> (1+x) + (x+2)
So ((1+) + (+2)) 3
равно (1+3) + (3+2)
, что равно 9, как указано ghci
.
map . map
аналогичен, как указано его типом, заданным ghci
:
(a -> b) -> [[a]] -> [[b]]
первый аргумент этой функции должен быть функцией типа a->b
,
вторым аргументом должен быть вложенный список типа [[a]]
, и это составлено
функция map . map
будет применять первый аргумент для каждого элемента каждого
list во втором аргументе, верните вложенный список типов [[b]]
. Для
Пример
*Main> (map . map) (+1) [[1,2], [3,4,5]]
[[2,3],[4,5,6]]