Используются ли монадные трансформаторы для получения услуг JSON?
У меня есть игра! 2 для приложения Scala, которому необходимо получить некоторые данные в формате JSON из внешней службы.
Игра! framework позволяет асинхронно делать HTTP-запросы, обертывая ответ в Promise. Promise
- это монада, которая обертывает значение, которое будет доступно в будущем.
Это хорошо, но в моем случае то, что я получаю от веб-службы, это строка JSON. Мне нужно разобрать его, и синтаксический анализ может завершиться неудачей. Поэтому я должен обернуть все, что попадаю в Option
. В результате многие мои методы возвращают Promise[Option[Whatever]]
. То есть значение типа Whatever
, которое, возможно, будет доступно позже.
Теперь, когда мне приходится работать над таким значением, мне нужно map
его дважды. Я думал об этом, следующим образом:
- создаем новый тип, скажем
Hope[A]
, который обертывает Promise[Option[A]]
- определение соответствующих методов, таких как
map
(или, может быть, я должен использовать foreach
и наследовать от некоторого символа коллекции?) и flatten
- предоставлять неявный конвертер между
Promise[Option[A]]
и Hope[A]
.
Легко определить map
- композиция двух функторов снова является функтором - и flatten
может быть сделано явно в этом случае или при составлении монады с Option
.
Но мое ограниченное понимание, что мне не нужно изобретать этот материал: трансформатор монады существует именно для этого случая. Или, ну, так я думаю - я никогда не использовал монад-трансформер - и в этом суть вопроса:
Могут ли трансиверы монады использоваться в этой ситуации? Как я могу их фактически использовать?
Ответы
Ответ 1
Используя библиотеку Scalaz library OptionT
, вы должны иметь возможность превращать значения типа Promise[Option[A]]
в значения типа OptionT[Promise, A]
.
Использование Scalaz 7:
import scalaz.OptionT._
val x: OptionT[Promise, Int] = optionT(Promise.pure(Some(123)))
Чтобы использовать это значение, например, для вызова map
или flatMap
на нем, вам нужно будет предоставить соответствующий тип для Promise
(Functor
для map
, Monad
для flatMap
).
Так как Promise
является монадическим, должно быть возможно предоставить экземпляр Monad[Promise]
. (Вы получите Functor
и Applicative
бесплатно, потому что классы типов образуют иерархию наследования.) Например (примечание: я не тестировал это!):
implicit val promiseMonad = new Monad[Promise] {
def point[A](a: => A): Promise[A] = Promise.pure(a)
def bind[A, B](fa: Promise[A])(f: A => Promise[B]): Promise[B] = fa flatMap f
}
В качестве простого примера теперь вы можете использовать map
в OptionT[Promise, A]
, чтобы применить функцию типа A => B
к значению внутри:
def foo[A, B](x: OptionT[Promise, A], f: A => B): OptionT[Promise, B] = x map f
Чтобы получить базовое значение Promise[Option[A]]
из OptionT[Promise, A]
, вызовите метод run
.
def bar[A, B](x: Promise[Option[A]], f: A => B): Promise[Option[B]] =
optionT(x).map(f).run
Вы получите большую выгоду от использования монадных трансформаторов, когда сможете составить несколько операций совместимых типов, сохраняя тип OptionT[Promise, _]
между операциями и извлекая базовое значение в конце.
Чтобы составить операции для понимания, вам понадобятся функции типа A => OptionT[Promise, B]
.
Ответ 2
- удален -
изменить:
Хорошо, вы можете просто использовать scalaz.OptionT
здесь:
val x = optionT(Promise { /* api call */ some("""{ "foo": "bar" }""") })
val mapped = x.map(Json.parse).run // run return the resulting Promise[Option[T]]