Не могу получить бессмысленную запись для компиляции в Haskell

Это работает

unique :: (a -> Bool) -> [a] -> Bool
unique p xs = 1 == length (filter p xs)

Но теперь я хочу это в виде:

unique = (== 1) . length . filter

Сообщение об ошибке:

Couldn't match expected type `[a] -> Bool' with actual type `Bool'
Expected type: b0 -> [a] -> Bool
  Actual type: b0 -> Bool
In the first argument of `(.)', namely `(== 1)'
In the expression: (== 1) . length . filter

Почему это не работает?

1 ответ

Решение

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

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

-- Important to make it the same precedence as (.)
infixr 9 .:

unique = ((== 1) . length) .: filter

Если вы посмотрите на тип (length .) в GHCi вы получите

(length .) :: (a -> [b]) -> a -> Int

Это означает, что она принимает функцию с одним аргументом, которая возвращает список. Если мы посмотрим на тип filter:

filter :: (a -> Bool) -> [a] -> [a]

Это может быть переписано, чтобы сделать это "единственным аргументом" как

filter :: (a -> Bool) -> ([a] -> [a])

И это совершенно ясно не совпадает с a -> [b]! В частности, компилятор не может понять, как сделать ([a] -> [a]) быть таким же, как [b], поскольку один - это функция в списках, а другой - просто список. Так что это источник ошибки типа.


Интересно, что .: Оператор может быть обобщен для работы с функторами:

(.:) :: (Functor f, Functor g) => (a -> b) -> f (g a) -> f (g b)
(.:) = fmap fmap fmap
-- Since the first `fmap` is for the (->) r functor, you can also write this
-- as (.:) = fmap `fmap` fmap === fmap . fmap

Для чего это хорошо? Скажи, что у тебя есть Maybe [[Int]]и вы хотели, чтобы сумма каждого подсписка внутри Justпри условии, что он существует:

> let myData = Just [[3, 2, 1], [4], [5, 6]]
> sum .: myData
Just [6, 4, 11]
> length .: myData
Just [3, 1, 2]
> sort .: myData
Just [[1,2,3],[4],[5,6]]

Или что, если у вас был [Maybe Int], и вы хотели увеличить каждый из них:

> let myData = [Just 1, Nothing, Just 3]
> (+1) .: myData
[Just 2,Nothing,Just 4]

Возможности продолжаются и продолжаются. По сути, он позволяет сопоставить функцию внутри двух вложенных функторов, и такая структура довольно часто появляется. Если у вас когда-либо был список внутри Maybeили кортежи внутри списка, или IO возвращая строку или что-то в этом роде, вы столкнулись с ситуацией, когда вы могли бы использовать (.:) = fmap fmap fmap,

Другие вопросы по тегам