Ошибка при попытке использовать композицию функций в Haskell - PullRequest
4 голосов
/ 20 февраля 2011

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

Я прочитал несколько вики на ., но мне довольно трудно заставить его работать правильно.Насколько я понимаю, он работает, выполняя функции b . a в алфавитном порядке и возвращая результат.Другими словами x = foo a, а затем foo b of x.Однако после применения нескольких «вариантов / возможностей» с помощью двух приведенных ниже функций фильтра я не могу заставить его скомпилировать из-за ошибок.

greaterThanOne :: Int -> Bool
greaterThanOne = (>1)

lessThanTen :: Int -> Bool
lessThanTen = (<10)

twoFilters :: [Int] -> [Int]
twoFilters xs= filter lessThanTen (filter greaterThanOne xs)

Эти две неудачные попытки, на которые я больше всего доверял;

twoFilters xs = filter (lessThanTen . greaterThanOne xs)

twoFilters xs = filter (lessThanTen xs . greaterThanOne xs)

Куда по рассуждениям я иду не так?

Ответы [ 7 ]

3 голосов
/ 20 февраля 2011

Войдите в чудесный мир аппликативных функторов:

import Control.Applicative

greaterThanOne = (>1)

lessThanTen = (<10)

twoFilters = filter ((&&) <$> greaterThanOne <*> lessThanTen)

twoFilters [1,2,3,4,5,6,7,8,9,10]
-- [2,3,4,5,6,7,8,9]

Прочитайте Узнайте вас Haskell - Аппликативные Функторы для подробного объяснения.

3 голосов
/ 20 февраля 2011

Попытки, в которых вы были уверены, являются простым провалом в вашей логике: оператор точки работает следующим образом:

(f.g)(x) = f(g(x))

Итак, попытка вычислить пример 5 дает:

lessThanThen(greaterThanOne(5)) = lessThanTen(True) -- that can't be right, can it???

То, что вы хотите, - это лямбда и &&:

filter (\x-> (lessThanThen x) && greaterThanOne(x))

В качестве альтернативы вы можете использовать два фильтра:

filter lessThanTen . filter greaterThanOne $

2 голосов
/ 20 февраля 2011

Вы не можете составить эти две функции, как это. f . g работает как композиция в математике, то есть эквивалентно f(g(x)). Это означает, что внешняя функция должна принимать аргумент типа, который возвращает внутренняя функция, в вашем случае внешняя функция должна быть Bool -> Bool.

Вы можете написать свой twoFilters используя оператор композиции следующим образом:

twoFilters = (filter lessThanTen) . (filter greaterThanOne)
1 голос
/ 20 февраля 2011

У вас это почти правильно. Я считаю, что самый простой способ начать изучение композиции функций с . - это сначала использовать $.

Итак, у вас есть список

twoFilters xs = xs

Вы хотите отфильтровать по greaterThanOne

twoFilters xs = filter greaterThanOne $ xs

Вы также хотите отфильтровать по lessThanTen

twoFilters xs = filter lessThanTen $ filter greaterThanOne $ xs

Теперь двигайтесь слева направо, заменяя все $ с ., за исключением последнего $

twoFilters xs = filter lessThanTen . filter greaterThanOne $ xs

Теперь вы можете использовать скобки вместо $:

twoFilters xs = (filter lessThanTen . filter greaterThanOne) xs

Или просто определите функцию pointfree:

twoFilters = filter lessThanTen . filter greaterThanOne

Я считаю, что версия в скобках - самая важная. Это показывает, что вы объединяете две частично примененные функции filter lessThanTen и filter greaterThanOne в одну функцию мегафильтрации, с помощью . и , а затем вы применяете список к ней. Вам необходимо заключить их в скобки, потому что . связывается менее плотно, чем приложение-функция, через пробел (пробел можно считать версией $ с высокой степенью надежности). Помните, что когда вы используете ., вы объединяете две функции в одну мега-функцию.

Уместно проверить подпись типа .

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

Функции, которые вы передаете, должны «выровняться» с очень специфическими сигнатурами типов (они должны быть совместимы для слияния). Но, честно говоря, ключ учится распознавать, когда приложение функции (с пробелом) связывается более тесно, чем вы предполагаете, и путает сигнатуры типов функций, которые вы пытаетесь составить. Во всяком случае, так было для меня.

1 голос
/ 20 февраля 2011

Насколько я понимаю, он работает, выполняя функции b . a в алфавитном порядке и возвращая результат.Другими словами x = foo a, а затем foo b of x

Это можно записать в Haskell как

let x = foo a in 
foo b x

(откуда взято foo?), Но правильно

(b . a) x = let y = a x in
            b y

Или, короче:

(b . a) x = b (a x)

Теперь, filter lessThanTen (filter greaterThanOne xs) имеет форму, аналогичную правой части этого определения, если вы помните, что вы могли бы написать это как (filter lessThanTen) ((filter greaterThanOne) xs):

((filter lessThanTen) . (filter greaterThanOne)) xs

Предположительно, что вы на самом деле хотите, это filter ??? xs, но этого должно быть достаточно для продолжения.

1 голос
/ 20 февраля 2011

Одна проблема в том, что приложение функции имеет наивысший приоритет. Таким образом, lessThanTen . greaterThanOne xs пытается составить lessThanTen с результатом greaterThanOne xs (который не работает с самого начала, функция работает с целыми числами, а не с их списками). Аналогично, lessThanTen xs. greaterThanOne xs пытается составить результаты этих вызовов функций (предполагая, что они имеют смысл в первую очередь), а не сами функции.

Другая проблема - неправильное понимание . - (f . g) x эквивалентно f (g x), то есть результат первой функции является аргументом для второй. Таким образом, тип g должен быть (a -> b), а тип f должен быть (b -> c) (оба b являются одной и той же переменной типа!). То, что вы хотите применить обе функции к одному и тому же аргументу и объединить результаты с &&. Насколько мне известно, для этого не существует никаких функций (по крайней мере, Google не нашел ничего для (a -> Bool) -> (a -> Bool) -> a -> Bool). Вам придется сделать свой собственный:

both f g x = f x && g x

В качестве альтернативы, вы можете просто дважды фильтровать (что не так плохо, как кажется благодаря ленивой оценке) - filter (>1) $ filter (<10) xs.

1 голос
/ 20 февраля 2011

(.) ожидает функцию, которая принимает один аргумент и возвращает значение, но вы передаете ему значение Bool в:

lessThanTen . greaterThanOne xs

что не так.

Здесь:

lessThanTen xs . greaterThanOne xs

вы пытаетесь составить два Bool значения, но вы должны были составить две функции, которые возвращают Bool значения.

...