Не могу получить бессмысленную запись для компиляции в 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
,