Как использовать 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
Другие вопросы по тегам