Haskell Language
Аппликативный метод
Поиск…
Вступление
Applicative
- это класс типов f :: * -> *
который позволяет использовать приложение отмененной функции над структурой, в которой функция также встроена в эту структуру.
замечания
Определение
class Functor f => Applicative f where
pure :: a -> f a
(<*>) :: f (a -> b) -> f a -> f b
Обратите внимание на ограничение Functor
на f
. pure
функция возвращает свой аргумент, встроенный в Applicative
структуру. Функция infix <*>
(произносится как «apply») очень похожа на fmap
за исключением функции, встроенной в Applicative
структуру.
Правильный экземпляр Applicative
должен удовлетворять прикладным законам , хотя они не применяются компилятором:
pure id <*> a = a -- identity
pure (.) <*> a <*> b <*> c = a <*> (b <*> c) -- composition
pure f <*> pure a = pure (f a) -- homomorphism
a <*> pure b = pure ($ b) <*> a -- interchange
Альтернативное определение
Поскольку каждый прикладной функтор является функтором , fmap
всегда можно использовать на нем; таким образом, сущность Аппликативного - это сопряжение несущего содержимого, а также способность его создавать:
class Functor f => PairingFunctor f where
funit :: f () -- create a context, carrying nothing of import
fpair :: (f a,f b) -> f (a,b) -- collapse a pair of contexts into a pair-carrying context
Этот класс изоморфен Applicative
.
pure a = const a <$> funit = a <$ funit
fa <*> fb = (\(a,b) -> a b) <$> fpair (fa, fb) = uncurry ($) <$> fpair (fa, fb)
Наоборот,
funit = pure ()
fpair (fa, fb) = (,) <$> fa <*> fb
Общие примеры применения
Может быть
Maybe
, это аппликативный функтор, содержащий возможно отсутствующее значение.
instance Applicative Maybe where
pure = Just
Just f <*> Just x = Just $ f x
_ <*> _ = Nothing
pure
поднимает данное значение в Maybe
, применяя Just
к нему. Функция (<*>)
применяет функцию, завернутую в « Maybe
к значению в « Maybe
. Если присутствуют как функция, так и значение (построено с помощью Just
), функция применяется к значению, и возвращается завернутый результат. Если либо отсутствует, вычисление не может продолжаться, и вместо него Nothing
не возвращается.
Списки
Один из способов, чтобы списки соответствовали сигнатуре типа <*> :: [a -> b] -> [a] -> [b]
- это декартово произведение двух списков, объединяющее каждый элемент первого списка с каждым элемент второго:
fs <*> xs = [f x | f <- fs, x <- xs]
-- = do { f <- fs; x <- xs; return (f x) }
pure x = [x]
Обычно это интерпретируется как эмулирующий недетерминизм со списком значений, стоящих для недетерминированного значения, возможные значения которого находятся над этим списком; поэтому комбинация двух недетерминированных значений охватывает все возможные комбинации значений в двух списках:
ghci> [(+1),(+2)] <*> [3,30,300]
[4,31,301,5,32,302]
Бесконечные потоки и zip-списки
Существует класс Applicative
который «застегивает» свои два входа вместе. Один простой пример - бесконечные потоки:
data Stream a = Stream { headS :: a, tailS :: Stream a }
Applicative
экземпляр Stream
применяет поток функций к потоку аргументов по-разному, сопоставляя значения в двух потоках по положению. pure
возвращает постоянный поток - бесконечный список одного фиксированного значения:
instance Applicative Stream where
pure x = let s = Stream x s in s
Stream f fs <*> Stream x xs = Stream (f x) (fs <*> xs)
Списки также допускают «zippy» Applicative
экземпляр, для которого существует ZipList
:
newtype ZipList a = ZipList { getZipList :: [a] }
instance Applicative ZipList where
ZipList xs <*> ZipList ys = ZipList $ zipWith ($) xs ys
Поскольку zip
обрезает свой результат в соответствии с самым коротким входом, единственная реализация pure
которая удовлетворяет Applicative
законам, - это та, которая возвращает бесконечный список:
pure a = ZipList (repeat a) -- ZipList (fix (a:)) = ZipList [a,a,a,a,...
Например:
ghci> getZipList $ ZipList [(+1),(+2)] <*> ZipList [3,30,300]
[4,32]
Две возможности напоминают нам внешний и внутренний продукт, аналогичный умножению матрицы 1 столбца ( nx 1
) с 1-строчной ( 1 xm
) в первом случае, в результате получается матрица nxm
(но сплющенная ); или умножения 1-строчной и 1-столбцовой матриц (но без суммирования) во втором случае.
функции
Когда они специализированы по функциям (->) r
, сигнатуры типа pure
и <*>
соответствуют S
комбинаторов K
и S
соответственно:
pure :: a -> (r -> a)
<*> :: (r -> (a -> b)) -> (r -> a) -> (r -> b)
pure
должен быть const
, а <*>
принимает пару функций и применяет их каждый к фиксированному аргументу, применяя два результата:
instance Applicative ((->) r) where
pure = const
f <*> g = \x -> f x (g x)
Функции - прототипный «zippy» аппликативный. Например, поскольку бесконечные потоки изоморфны (->) Nat
, ...
-- | Index into a stream
to :: Stream a -> (Nat -> a)
to (Stream x xs) Zero = x
to (Stream x xs) (Suc n) = to xs n
-- | List all the return values of the function in order
from :: (Nat -> a) -> Stream a
from f = from' Zero
where from' n = Stream (f n) (from' (Suc n))
... представление потоков по более высокому порядку создает автоматически Applicative
экземпляр zippy.