2017-02-07 13 views
6

Я хотел бы, чтобы составить оператор сложения (+) сделать функцию этого типа:«цепочка» оператора сложения в функцию, которая принимает 3 аргумента?

Num a => a -> a -> a -> a 

Мол, эквивалент этого:

(\a b c -> a + b + c) 

, но без необходимости прибегать к лямбды.


Я уже

((+) . (+)) 

попытался который я ожидал бы работать, но на удивление не было.

+0

Iirc, самообладание требует, чтобы функция принимала только один аргумент. Каждый бинарный оператор принимает 2 – Carcigenicate

+0

И почему бы не просто свернуть его с помощью '+'? – Carcigenicate

+0

@ Карциген, хм, это странно. Я бы не ожидал, что так будет. Есть ли способ сделать это? - отредактируйте: и что складывается, и какой список? – theonlygusti

ответ

11

http://pointfree.io дает точечную версию \a b c -> a + b + c как ((+) .) . (+).

Неофициально композиция работает только «интуитивно» для функций первого порядка, которые не принимают функции как аргументы и не возвращают функции в качестве значений. (+) - функция более высокого порядка; он принимает значение типа Num a => a и возвращает функцию типа Num a => a -> a. При попытке сочинить функции высшего порядка в наивной манере, результат не то, что вы ожидаете:

:t (+) . (+) 
(+) . (+) :: (Num a, Num (a -> a)) => a -> (a -> a) -> a -> a 

Рассмотрим определения двух функций:

(+) :: Num z => z -> z -> z 
(.) :: (b -> c) -> (a -> b) -> (a -> c) 
f . g = \x -> f (g x) 

Тогда

(+) . (+) == (.) (+) (+) 
      == \x -> (+) ((+) x) 

Из-за currying вы завершаете функцию, а не число, как первый аргумент первого (+).


Так как же мы получаем от h a b c = a + b + c до h = ((+) .) . (+)? Начните с перезаписи выражения infix в качестве префиксного выражения, используя тот факт, что (+) является лево-ассоциативным.

\a b c -> a + b + c 
    == \a b c -> ((+) a b) + c 
    == \a b c -> (+) ((+) a b) c 

Далее мы поочередно применяем преобразование eta для исключения аргумента и композиции для перемещения аргумента в положение, которое необходимо устранить. Я попытался очень четко определить функции, используемые для применения композиции.

 == \a b -> (+) ((+) a b)  -- eta conversion to eliminate c 
    == \a b -> (+) (((+) a) b) -- parentheses justified by currying 
    --   f  g   -- f = (+), g = ((+) a) 
    -- \a b -> f ( g b) 
    -- \a b -> (f . g) b  -- definition of (.) 
    == \a b -> ((+) . ((+) a)) b 
    == \a -> (+) . ((+) a)  -- eta conversion to eliminate b 
    == \a -> (.) (+) ((+) a)  -- prefix notation 
    == \a -> ((.) (+)) ((+) a) -- parentheses justified by currying 
    == \a -> ((+) .)((+) a)  -- back to a section of (.) 
    --   f  g  -- f = ((+) .), g = (+) 
    -- \a ->  f  (g a) 
    -- \a -> ( f . g) a  -- definition of (.) 
    == \a -> (((+) .) . (+)) a 
    == ((+) .) . (+)    -- eta conversion to eliminate a 
+0

Было бы полезно проанализировать работу '((+).). (+) '. –

6

Хотя это вносит некоторый шум, вы могли бы использовать uncurry :: (a -> b -> c) -> (a,b) -> c и curry :: ((a,b) -> c) -> a -> b -> c для временного хранения аргументов второго плюса в одном наборе:

curry $ (+) . uncurry (+) :: Num a => a -> a -> a -> a 

или, возможно, более семантический читаемый:

curry ((+) . uncurry (+)) :: Num a => a -> a -> a -> a 

uncurry таким образом, выполняет функцию (здесь (+)) и преобразует ее в функцию: uncurry (+) :: Num a => (a,a) -> a. Таким образом, вы преобразовали (+) в функцию , которая берет кортеж.

Теперь мы можем использовать (.), чтобы сделать композицию с первым (+):

(+) . uncurry (+) :: Num a => (a,a) -> (a -> a) 

Так что теперь у нас есть функция, которая принимает один аргумент (кортеж (a,a)) и производит функцию, которая принимает a (второй операнд первого (+)) и вычисляет сумму. Проблема, конечно, в том, что мы хотим избавиться от кортежа. Мы можем сделать это, передав функцию в curry. Это преобразует функцию tuple ((a,a) -> (a -> a)) в функцию, принимающую аргументы отдельно (a -> (a -> (a -> a))).

4

Обратите внимание на подпись оператора функции состава:

(.) :: (b -> c) -> (a -> b) -> a -> c 
     ^  ^  ^
       Functions 

Это занимает 2 функции, каждая из которых принимают 1 аргумент, и возвращает функцию, которая принимает аргумент одного и того же типа, что и второй функции , и возвращает тот же тип, что и первый.

Ваша попытка составить два + s не сработала, так как + принимает 2 аргумента, поэтому без какого-либо хакерского/творческого обходного пути это невозможно.

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

Если вы хотите суммировать несколько номеров, вы могли бы написать функцию, как:

sum :: [Int] -> Int 
sum nums = foldl (+) 0 nums 

Или, так как nums появляется в задней части определения, он может быть удален в целом, уступая «навел бесплатно»форма:

sum :: [Int] -> Int 
sum = foldl (+) 0 

Это уменьшает/складывает + по списку номеров. Если вы еще не использовали складки, смотрите в них сейчас. Они являются одним из основных способов достижения цикла в Haskell. Это, по сути, «неявная рекурсия», когда вы имеете дело со списками или что-то еще итеративное.

С выше функции, определенной, вы можете использовать его как:

sum [1, 2 3, 4, 5] 
+0

Я думаю, что вы должны написать 'foldr (+) 0 nums' (с скобками) и' = 'вместо' :: 'в присваивании. Как правило, более эффективно использовать память 'foldl' над' foldr', поскольку функция коммутативна. –

+0

@WillemVanOnsem Упс. Idk, как я закончил использование двоеточий вместо равных. И спасибо, я поменяю складку. – Carcigenicate

+0

вам все равно нужно положить скобки над '(+)', иначе Haskell видит это как '(+) foldl 0' ... –

8

Вам нужен этот странный оператор (.).(.), который иногда определяет как .: (думаю, из 3-х точек ...)

В GHCi

Prelude> let (.:) = (.).(.) 
Prelude> let f = (+) .: (+) 
Prelude> f 1 2 3 
> 6 

Примечание этот оператор также может быть определен как <$$> = fmap . fmap.