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

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


person kaibakker    schedule 03.09.2014    source источник


Ответы (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.

person bheklilr    schedule 03.09.2014
comment
unique = (== 1) .: length .: filter тоже возможно. С другой стороны, (== 1) . length .: filter :: (Num ([a] -> Int), Eq ([a] -> Int)) => (a -> Bool) -> Bool может проверять тип, но бесполезен. - person Lambdageek; 05.09.2014