Ответ 1
Аппликативные функторы позволяют проводить статический анализ во время выполнения. Это лучше объясняется более простым примером.
Представьте, что вы хотите вычислить значение, но хотите отслеживать зависимости, которые имеет значение. Например, вы можете использовать IO a
для вычисления значения и иметь список Strings
для зависимостей:
data Input a = Input { dependencies :: [String], runInput :: IO a }
Теперь мы можем легко сделать это экземпляром Functor
и Applicative
. Экземпляр функтора тривиален. Поскольку он не вводит никаких новых зависимостей, вам просто нужно сопоставить значение runInput
:
instance Functor (Input) where
fmap f (Input deps runInput) = Input deps (fmap f runInput)
Экземпляр Applicative
более сложный. функция pure
просто вернет значение без зависимостей. Комбайнер <*>
объединяет два списка зависимостей (удаление дубликатов) и объединяет два действия:
instance Applicative Input where
pure = Input [] . return
(Input deps1 getF) <*> (Input deps2 runInput) = Input (nub $ deps1 ++ deps2) (getF <*> runInput)
С этим мы также можем сделать Input a
экземпляр Num, если Num a
:
instance (Num a) => Num (Input a) where
(+) = liftA2 (+)
(*) = liftA2 (*)
abs = liftA abs
signum = liftA signum
fromInteger = pure . fromInteger
Далее, давайте сделаем пару входов:
getTime :: Input UTCTime
getTime = Input { dependencies = ["Time"], runInput = getCurrentTime }
-- | Ideally this would fetch it from somewhere
stockPriceOf :: String -> Input Double
stockPriceOf stock = Input { dependencies = ["Stock ( " ++ stock ++ " )"], runInput = action } where
action = case stock of
"Apple" -> return 500
"Toyota" -> return 20
Наконец, давайте сделаем значение, которое использует некоторые входы:
portfolioValue :: Input Double
portfolioValue = stockPriceOf "Apple" * 10 + stockPriceOf "Toyota" * 20
Это довольно крутое значение. Во-первых, мы можем найти зависимости portfolioValue
как чистое значение:
> :t dependencies portfolioValue
dependencies portfolioValue :: [String]
> dependencies portfolioValue
["Stock ( Apple )","Stock ( Toyota )"]
Это статический анализ, который позволяет Applicative
- мы знаем зависимости без необходимости выполнять действие.
Мы все равно можем получить значение действия:
> runInput portfolioValue >>= print
5400.0
Теперь, почему мы не можем сделать то же самое с Monad
? Причина в том, что Monad
может выразить выбор, поскольку одно действие может определить, каким будет следующее действие.
Представьте, что для Input
был Monad
интерфейс, и у вас был следующий код:
mostPopularStock :: Input String
mostPopularStock = Input { dependencies ["Popular Stock"], getInput = readFromWebMostPopularStock }
newPortfolio = do
stock <- mostPopularStock
stockPriceOf "Apple" * 40 + stockPriceOf stock * 10
Теперь, как мы можем вычислить зависимости newPortolio
? Оказывается, мы не можем сделать это, не используя IO! Это будет зависеть от самого популярного запаса, и единственный способ узнать - запустить действие IO. Поэтому невозможно статически отслеживать зависимости, когда тип использует Monad, но полностью возможен с помощью только аппликативного. Это хороший пример того, почему часто меньшая мощность означает более полезную, поскольку Аппликатив не позволяет выбирать, зависимости могут быть рассчитаны статически.
Изменить: Что касается проверки того, является ли y
рекурсивным сам по себе, такая проверка возможна с аппликативными функторами, если вы хотите аннотировать имена ваших функций.
data TrackedComp a = TrackedComp { deps :: [String], recursive :: Bool, run :: a}
instance (Show a) => Show (TrackedComp a) where
show comp = "TrackedComp " ++ show (run comp)
instance Functor (TrackedComp) where
fmap f (TrackedComp deps rec1 run) = TrackedComp deps rec1 (f run)
instance Applicative TrackedComp where
pure = TrackedComp [] False
(TrackedComp deps1 rec1 getF) <*> (TrackedComp deps2 rec2 value) =
TrackedComp (combine deps1 deps2) (rec1 || rec2) (getF value)
-- | combine [1,1,1] [2,2,2] = [1,2,1,2,1,2]
combine :: [a] -> [a] -> [a]
combine x [] = x
combine [] y = y
combine (x:xs) (y:ys) = x : y : combine xs ys
instance (Num a) => Num (TrackedComp a) where
(+) = liftA2 (+)
(*) = liftA2 (*)
abs = liftA abs
signum = liftA signum
fromInteger = pure . fromInteger
newComp :: String -> TrackedComp a -> TrackedComp a
newComp name tracked = TrackedComp (name : deps tracked) isRecursive (run tracked) where
isRecursive = (name `elem` deps tracked) || recursive tracked
y :: TrackedComp [Int]
y = newComp "y" $ liftA2 (:) x z
x :: TrackedComp Int
x = newComp "x" $ 38
z :: TrackedComp [Int]
z = newComp "z" $ liftA2 (:) 3 y
> recursive x
False
> recursive y
True
> take 10 $ run y
[38,3,38,3,38,3,38,3,38,3]