Что есть ((+). (+)) В 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]]