Как использовать Applicative для параллелизма?
Это продолжение моего предыдущего вопроса. Я скопировал пример ниже с Haxl
Предположим, что я получаю данные с сервера блогов для отображения страницы блога, которая содержит последние сообщения, популярные сообщения и темы сообщений.
У меня есть следующий API извлечения данных:
val getRecent : Server => Seq[Post] = ...
val getPopular : Server => Seq[Post] = ...
val getTopics : Server => Seq[Topic] = ...
Теперь мне нужно составить их для реализации новой функции getPageData
val getPageData: Server => (Seq[Post], Seq[Post], Seq[Topic])
Хаксл предлагает использовать новую монаду Fetch
сделать API-интерфейс компонуемым.
val getRecent : Fetch[Seq[Posts]] = ...
val getPopular : Fetch[Seq[Posts]] = ...
val getTopics : Fetch[Seq[Topic]] = ...
Теперь я могу определить мой getPageData: Fetch[A]
с монадическим составом
val getPageData = for {
recent <- getRecent
popular <- getPopular
topics <- getTopics
} yield (recent, popular, topics)
но это не работает getRecent
, getPopular
, а также getTopics
одновременно.
Хаксл предлагает использовать аппликативную композицию <*>
составлять "параллельные" функции (то есть функции, которые могут выполняться одновременно). Итак, мои вопросы:
- Как реализовать
getPageData
при условии,Fetch[A]
являетсяApplicative
? - Как реализовать
Fetch
какApplicative
но неMonad
?
1 ответ
Как реализовать getPageData, предполагая, что Fetch[A] является Applicative?
Все, что нам нужно сделать, это сбросить монадическую привязку >>=
в пользу аппликативного <*>
, Так что вместо
val getPageData = for {
recent <- getRecent
popular <- getPopular
topics <- getTopics
} yield (recent, popular, topics)
мы бы написали что-то вроде (в синтаксисе Haskell; извините, я не могу сделать Scala на макушке):
getPageData = makeTriple <$> getRecent <*> getPopular <*> getTopics
where
makeTriple x y z = (x, y, z)
Но имеет ли это желаемый эффект, зависит от второго вопроса!
Как реализовать Fetch как Аппликатив, а не Монаду?
Ключевое различие между монадическим и аппликативным секвенированием заключается в том, что монадное может зависеть от значения внутри монадического значения, тогда как аппликативное <*>
не могу. Обратите внимание, как монадическое выражение для getPageData
выше связывает имена recent
а также popular
до достижения getTopics
, Эти имена могли быть использованы для изменения структуры выражения, например, путем получения другого источника данных в случае recent
пустой. Но с аппликативным выражением, результаты getRecent
а также getPopular
не являются факторами в структуре самого выражения. Это свойство позволяет нам запускать каждый термин в аппликативном выражении одновременно, потому что мы знаем структуру выражения статически.
Таким образом, используя приведенное выше наблюдение и, очевидно, конкретную форму типа данных Fetch, мы можем найти подходящее определение для <*>
, Я думаю, что следующее иллюстрирует общую идею:
data Fetch a = Fetch { runFetch :: IO a }
fetchF <*> fetchX = Fetch $ do
-- Fire off both IOs concurrently.
resultF <- async $ runFetch fetchF
resultX <- async $ runFetch fetchX
-- Wait for both results to be ready.
f <- wait resultF
x <- wait resultX
return $ f x
Для сравнения предположим, что мы попытались выполнить монадное связывание с параллельной оценкой:
fetchF >>= fetchK = Fetch $ do
resultF <- async $ runFetch fetchF
-- Oh no, we need resultF in order to produce the next
-- Fetch value! We just have to wait...
f <- wait resultF
fetchX <- async $ runFetch (fetchK f)
x <- wait $ runFetch fetchX
return $ f x