Как я могу изменить поток Option Monad? - PullRequest
8 голосов
/ 15 ноября 2010

скажем, у меня есть куча «проверочных» функций, которые возвращают None, если нет ошибки, в противном случае он возвращает Some (String), указывающий сообщение об ошибке. Что-то вроде следующего ...

def validate1:Option[String] 
def validate2:Option[String]
def validate3:Option[String]

Я собираюсь вызвать их по очереди, и как только кто-то вернет Some (String), я остановлюсь и верну то же самое. Если он возвращает None, я перехожу к следующему, пока последовательность не закончится. Если все они возвращают None, я возвращаю None.

Я бы хотел склеить их вместе "для выражения". Что-то вроде ...

for( a <- validate1; b <- validate2; c <- validate3) yield None;

Тем не менее, Option выглядит совершенно противоположно тому, что я хочу здесь. Он останавливается на None и следует за Some (String).

Как мне достичь чего-то подобного?

Ответы [ 4 ]

17 голосов
/ 15 ноября 2010

Вы можете просто связать вызовы вместе с методом илиElse в Option

validate1 orElse validate2 orElse validate3

или вы можете выполнить сгиб по коллекции методов проверки, преобразованных в функции

val vlist= List(validate1 _, validate2 _, validate3 _)

vlist.foldLeft(None: Option[String]) {(a, b) => if (a == None) b() else a}
3 голосов
/ 16 ноября 2010

Библиотека scalaz имеет тип Validation, который допускает невероятную гимнастику с созданием как ошибок, так и успеха. Например, предположим, что у вас есть несколько методов, которые могут либо вернуть сообщение fail , либо какой-либо успешный результат (A / B / C):

import scalaz._; import Scalaz._
def fooA : ValidationNEL[String, A]
def fooB : ValidationNEL[String, B]
def fooC : ValidationNEL[String, C]

Их можно использовать с аппликативным функтором для объединения вызовов:

(foo1 <|**|> (foo2, foo3)) match {
  case Success( (a, b, c) ) => //woot
  case Failure(msgs)        => //erk
}

Обратите внимание, что если произойдет сбой любого из foo1/2/3, произойдет сбой всей композиции с непустым списком (NEL) сообщений об ошибках. Если происходит сбой более чем одного, вы получаете все сообщения об ошибках.

Это убийственное приложение. Примеры того, как вернуть успех и неудачу, следующие:

def foo1 : ValidationNEL[String, Int] = 1.success
def foo2 : ValidationNEL[String, Double] = "some error msg".failNel
2 голосов
/ 15 ноября 2010

Разве вы не можете просто объединить итераторы и затем взять первый элемент?Что-то вроде:

scala> def validate1: Option[String] = {println("1"); None}
scala> def validate2: Option[String] = {println("2"); Some("error")}
scala> def validate3: Option[String] = {println("3"); None}
scala> (validate1.iterator ++ validate2.iterator ++ validate3.iterator).next
1
2
res5: String = error
0 голосов
/ 15 ноября 2010

Я думаю, что вы могли бы выиграть от использования Box Lift , который имеет Full (то есть Some), Empty (то есть None) и Failure (Empty спричина, почему это пусто, и это может быть приковано цепью).У Дэвида Поллака есть хорошее сообщение в блоге , представляющее его.Короче говоря, вы можете сделать что-то вроде этого (не проверено):

def validate1: Box[String]
def validate2: Box[String]
def validate3: Box[String]
val validation = for (
  validation1 <- validate1 ?~ "error message 1"
  validation2 <- validate2 ?~ "error message 2"
  validation3 <- validate3 ?~ "error message 3"
) yield "overall success message"

Это не короче, чем оригинальный пример, но, на мой взгляд, немного более логично с результатомуспешная проверка в Full и неудачная проверка в Failure.

Однако мы можем получить меньше.Во-первых, поскольку наша функция проверки возвращает Box[String], они могут возвращать Failure s сами, и нам не нужно преобразовывать Empty в Failure самих:

val validation = for (
  validation1 <- validate1
  validation2 <- validate2
  validation3 <- validate3
) yield "overall success message"

Но, Boxтакже есть метод or, который возвращает тот же Box, если это Full, или другой Box, если это не так.Это даст нам:

val validation = validate1 или validate2 или validate3

Однако эта строка останавливается при первой проверке success , а не при первой ошибке.Возможно, имеет смысл создать другой метод, который делает то, что вы хотите (возможно, под названием unless?), Хотя я не могу сказать, что он действительно был бы гораздо более полезным, чем подход для понимания.

Однако вотнебольшая библиотека, которая делает это:

scala> class Unless[T](a: Box[T]) {
     | def unless(b: Box[T]) = {
     | if (a.isEmpty) { a }
     | else b
     | }
     | }
defined class Unless

scala> implicit def b2U[T](b: Box[T]): Unless[T] = new Unless(b)
b2U: [T](b: net.liftweb.common.Box[T])Unless[T]

scala> val a = Full("yes")                                      
a: net.liftweb.common.Full[java.lang.String] = Full(yes)

scala> val b = Failure("no")                                    
b: net.liftweb.common.Failure = Failure(no,Empty,Empty)

scala> val c = Full("yes2")                                     
c: net.liftweb.common.Full[java.lang.String] = Full(yes2)

scala> a unless b
res1: net.liftweb.common.Box[java.lang.String] = Failure(no,Empty,Empty)

scala> a unless b unless c
res2: net.liftweb.common.Box[java.lang.String] = Failure(no,Empty,Empty)

scala> a unless c unless b
res3: net.liftweb.common.Box[java.lang.String] = Failure(no,Empty,Empty)

scala> a unless c
res4: net.liftweb.common.Box[java.lang.String] = Full(yes2)

Это быстрый взлом, основанный на моем ограниченном понимании системы типов Scala, как вы можете видеть в следующей ошибке:

scala> b unless a
<console>:13: error: type mismatch;
 found   : net.liftweb.common.Full[java.lang.String]
 required: net.liftweb.common.Box[T]
       b unless a
                ^

Тем не менее, этого должно быть достаточно, чтобы вы встали на правильный путь.

Конечно, Подъемник ScalaDocs содержит больше информации о Box .

...