Это очень похоже на то, что уже было сказано, но побалуйте меня на мгновение и позвольте мне проповедовать вам значение типов.
import Control.Applicative
ifte :: Bool -> a -> a -> a
ifte b t f = if b then t else f
compress :: Eq a => [a] -> [a]
-- compress = ifte <$> cond <$> t <*> f
-- We will leave compress undefined so we can load this into ghci.
-- After some trial and error it is clear that this is the part
-- that doesn't type check
compress = undefined
cond :: Eq a => [a] -> Bool
cond = (==) <$> head <*> head . tail
t :: Eq a => [a] -> [a]
t = (compress .) . (:) <$> head <*> tail . tail
f :: Eq a => [a] -> [a]
f = (:) <$> head <*> compress . tail
Здесь я выделил это, и, как упомянул Брэндон, в этот момент вы должны увидеть, что ошибка заключается в использовании <$>
, где должно быть <*>
. Вы освоитесь с этой концепцией, продолжая изучать аппликативный стиль, когда ваши выражения обычно имеют один <$>
, за которым следует произвольный # из <*>
:
f <$> a <*> b <*> c <*> d <*> ...
Игнорируя это понимание, я дал каждому подвыражению здесь тип и TLD, временно. Это позволяет мне загрузить файл в ghci и немного поиграть.
ghci> :t ifte <$> cond <$> t <*> f
... Eq a => [a] -> [a] -> [a]
ВТФ ??? Это типа звук ??? Предполагалось, что это даст ошибку, но, видимо, с этим выражением все в порядке. Либо это? Обратите внимание, что подпись этого типа не соответствует той, которую мы хотим для compress
.
ghci> :t compress
... Eq a => [a] -> [a]
Подвыражения соответствуют сигнатурам типов, которые мы ожидаем от них, о чем свидетельствует тот факт, что компилятор нас не рвёт. Поскольку это так, проблема заключается в том, как мы их объединяем. Итак, какие части мы хотим объединить здесь? Игнорирование ограничений Eq:
ifte :: Bool -> [a] -> [a] -> [a]
cond :: [a] -> Bool
t :: [a] -> [a]
f :: [a] -> [a]
-- desired result
:: [a] -> [a]
Здесь я сделал тривиальную специализацию ifte
для [a]
вместо любых a
. Соотношение ясное: типы вывода cond
, t
и f
соответствуют типам ввода ifte
. Нам просто нужно передать все эти три выражения одним и тем же [a]
. Признавая, что (input ->)
является аппликативным, мы обобщаем:
arg1 :: (a -> b -> c -> d)
arg2 :: f a
arg3 :: f b
arg4 :: f c
res :: f d
-- for our case,
-- f = ([a] ->)
-- a = Bool
-- b = [a]
-- c = [a]
-- d = [a]
Стоп ... Hoogle time! Hoogling (a -> b -> c -> d) -> f a -> f b -> f c -> f d
, мы сразу находим liftA3
, что неудивительно определено:
liftA3 f a b c = f <$> a <*> b <*> c