2012-02-24 24 views
14

Tôi muốn sử dụng Scalaz để xác thực và muốn có thể sử dụng lại các hàm xác thực trong các ngữ cảnh khác nhau. Tôi hoàn toàn mới đối với Scalaz btw.Soạn xác nhận Scalaz

Hãy nói rằng tôi có những kiểm tra đơn giản:

def checkDefined(xs: Option[String]): Validation[String, String] = 
    xs.map(_.success).getOrElse("empty".fail) 

def nonEmpty(str: String): Validation[String, String] = 
    if (str.nonEmpty) str.success else "empty".fail 

def int(str: String): Validation[String, Int] = ... 

Tôi thích để có thể soạn kiểm chứng thực nơi đầu ra từ một được đưa vào khác. Tôi có thể dễ dàng làm điều đó với flatMap hoặc thông qua để hiểu, nhưng nó cảm thấy như có phải là một cách tốt hơn.

for { 
    v1 <- checkDefined(map.get("foo")) 
    v2 <- nonEmpty(v1) 
    v3 <- int(v2) 
    v4 <- ... 
} yield SomeCaseClass(v3, v4) 

hoặc

val x1 = checkDefined(map get "foo").flatMap(nonEmpty).flatMap(int) 
val x2 = check(...) 

// How to combine x1 and x2? 

Bất kỳ suy nghĩ từ các chuyên gia Scalaz out there?

+1

những gì về "(x1 | @ | x2) {(x1, x2) => ...}" Tôi không như vậy chắc chắn về cú pháp chính xác mặc dù ... Xem http://www.casualmiracles.com/2012/01/16/a-small-example-of-applicative-functors-with-scalaz/ – Jan

Trả lời

13

Bạn có thể muốn có một cái nhìn tại Tale of Three Nightclubs trong đó mô tả thành phần xác nhận sử dụng:

  1. Monads (tức flatMap)
  2. functors applicative hai cách (sử dụng |@|traverse)

Về cơ bản, số lượng quy tắc cho điều này: thành phần qua monads là không nhanh. Tức là, tính toán của bạn sẽ ngắn mạch tại thời điểm này và giải quyết thành một Failure(e). Sử dụng functors ứng dụng có nghĩa là bạn có thể tích lũy thất bại (có thể để xác thực biểu mẫu web) - mà bạn thực hiện bằng cách sử dụng collection (là Semigroup) làm loại lỗi - ví dụ canconical sử dụng NonEmptyList.

Có công cụ hữu ích khác trên Validation cũng như:

val1 <+> val2 //Acts like an `orElse` 
val1 >>*<< val2 //Accumulates both successes and failures 

Trong ví dụ cụ thể của bạn, tại sao bạn nghĩ rằng có "phải là một cách tốt hơn" so với làm việc đó thông qua một cho-hiểu? Nó có thể được cải thiện đôi chút, mặc dù:

def checkDefined(xs: Option[String]) = xs.toSuccess("empty :-(") 

Trong trường hợp đó, nó không thực sự xứng đáng với một phương pháp toàn bộ:

for { 
    v1 <- map get "foo" toSuccess "Empty :-(" 
    v2 <- some(v1) filterNot (_.isEmpty) toSuccess "Empty :-(" 
    v3 <- (v2.parseInt.fail map (_.getMessage)).validation 
    v4 <- ... 
} yield SomeCaseClass(v3, v4) 
+0

Thực ra tôi có ý chính của bạn trong một tab đang mở ngay bây giờ. Đó là một ví dụ rất hay. Điều tôi đang đấu tranh là tôi muốn kiểm tra của tôi để soạn. I E. đầu ra từ một kiểm tra nên là đầu vào kế tiếp. Như trong ví dụ của bạn khi bạn có chức năng kiểm tra của bạn trong một danh sách tất cả họ làm kiểm tra trên cùng một cá thể người khác với những gì tôi đang cố gắng để làm. – chrsan

+0

Bạn đang mô tả 'flatMap'; tức là bạn đã biết câu trả lời! –

+0

Cảm ơn rất nhiều! Không có trong trường hợp này nó không cần một phương pháp toàn bộ, nhưng tôi cần phải xác nhận điều tương tự trong các bối cảnh khác nhau. I E. một id trong chính nó và một thực thể có một id và một số lĩnh vực khác là tốt. Bằng cách "một cách tốt hơn" tôi có nghĩa là các phương pháp ngắn gọn trông giống như những gì bạn mô tả ở trên mà không phải là hiển nhiên đối với chúng tôi với một nền tảng bắt buộc muốn được chức năng hơn vv – chrsan

17

Ngoài các giải pháp được đề xuất bởi @oxbow_lakes, bạn cũng có thể sử dụng Thành phần Kleisli.

scala> import scalaz._, Scalaz._ 
import scalaz._ 
import Scalaz._ 

scala> def f: Int => Validation[String, Int] = i => if(i % 2 == 0) Success(i * 2) else Failure("Odd!") 
f: Int => scalaz.Validation[String,Int] 

scala> def g: Int => Validation[String, Int] = i => if(i > 0) Success(i + 1) else Failure("Not positive!") 
g: Int => scalaz.Validation[String,Int] 

scala> type Va[+A] = Validation[String, A] 
defined type alias Va 

scala> import Validation.Monad._ 
import Validation.Monad._ 

scala> kleisli[Va, Int, Int](f) >=> kleisli[Va, Int, Int](g) 
res0: scalaz.Kleisli[Va,Int,Int] = [email protected] 

scala> res0(11) 
res1: Va[Int] = Failure(Odd!) 

scala> res0(-4) 
res2: Va[Int] = Failure(Not positive!) 

scala> res0(4) 
res3: Va[Int] = Success(9) 

Một chức năng của loại A => M[B] nơi M : Monad được gọi là một mũi tên Kleisli.

Bạn có thể soạn hai mũi tên Kleisli A => M[B]B => M[C] để nhận mũi tên A => M[C] sử dụng toán tử >=>. Điều này được gọi là thành phần Kleisli.

Biểu thức kleisli(f) >=> kleisli(g) >=> kleisli(h) tương đương với x => for(a <- f(x); b <- g(a); c <- h(b)) yield c, trừ các ràng buộc cục bộ không cần thiết.

+2

Oh cho suy luận hàm tạo kiểu áp dụng một phần! –

+0

@oxbow_lakes, đó là một trong những điều cần thiết nhất trong Scala. Đáng tiếc là nó không xuất hiện trong danh sách ngắn hạn của họ. – missingfaktor

+1

như @oxbow_lakes chỉ ra, có một cách tiếp cận ngắn mạch cũng như một cách tiếp cận tích lũy. Ví dụ này là cách tiếp cận ngắn mạch. Làm thế nào điều này sẽ được thực hiện nếu bạn muốn tích lũy những thất bại? – OleTraveler

0

Biểu

for { 
    v1 <- checkDefined(map.get("foo")) 
    v2 <- nonEmpty(v1) 
    v3 <- int(v2) 
    v4 <- someComputation() 
} yield SomeCaseClass(v3, v4) 

coulde được thay thế theo cách như vậy

(checkDefined(map.get("foo")).liftFailNel |@| nonEmpty(v1)) {(v1, v2) = 
    SomeCaseClass(int(v2), someComputation) 
} 

và kết quả sẽ là

Validtion[NonEmptyList[String], SomeCaseClass] which is equal to ValidationNEL[String, SomeCaseClass] 

nếu cả hai xác nhận thất bại, NonEmptyList sẽ chứa cả trong số họ

+1

Er, không có nó không thể.Một kết quả thành công cho xác nhận đầu tiên là cần thiết như đầu vào thứ hai, do đó, functors ứng dụng không thể giúp bạn ở đây –

0

Gần đây tôi đã mã hóa một "khung" đơn giản cho các xác thực khai báo có thể được tổng hợp. Ban đầu tôi đã thực hiện dựa trên câu trả lời của @ missingfaktor, tuy nhiên, trên đầu trang của những gì anh ta đưa ra, tôi đã thêm một DSL bằng cách sử dụng Shapeless's Generic để làm việc với các bộ kích cỡ tùy ý của các đầu vào được xác thực được nạp vào các chức năng của phù hợp với tinh thần.

sử dụng của nó là như sau:

def nonEmpty[A] = (msg: String) => Vali { a: Option[A] => 
    a.toSuccess(msg) 
} 

def validIso2CountryCode = (msg: String) => Vali { x: String => 
    IsoCountryCodes2to3.get(x).toSuccess(msg) 
} 

val postal = "12345".some 
val country = "GB".some 

val params = (
    postal 
    |> nonEmpty[String]("postal required"), 
    country 
    |> nonEmpty[String]("country required") 
    >=> validIso2CountryCode("country must be valid") 
) 

// parameter type inference doesn't work here due to the generic type level nature of the implementation; any improvements are welcome! 
validate(params) { (postal: String, country: String) => 
    println(s"postal: $postal, country: $country") 
} 

Việc thực hiện có thể được tìm thấy tại https://gist.github.com/eallik/eea6b21f8e5154e0c97e.

0

Ngoài câu trả lời của thiếu sót, người ta có thể lưu ý rằng scalaz 7 không có Monad cho Validation do không khớp với hành vi của nó với trường hợp Apply. Vì vậy, người ta có thể xác định Bind cho Validation, cùng với bộ chuyển đổi cho tiện theo dõi:

import scalaz.{Bind, Kleisli, Validation, Success, Failure} 

implicit def toKleisli[E, A, B](f: A => Validation[E, B]): Kleisli[Validation[E, ?], A, B] = 
    Kleisli[Validation[E, ?], A, B](f) 

implicit def fromKleisli[E, A, B](f: Kleisli[Validation[E, ?], A, B]): A => Validation[E, B] = f.run 

implicit def validationBind[E] = new Bind[Validation[E, ?]] { 

    def bind[A, B](fa: Validation[E, A])(f: (A) => Validation[E, B]): Validation[E, B] = { 
    import Validation.FlatMap._ 
    fa.flatMap(f) 
    } 

    def map[A, B](fa: Validation[E, A])(f: (A) => B): Validation[E, B] = fa.map(f) 
} 

val parse: Option[String] => Validation[String, Int] = checkDefined _ >=> nonEmpty _ >=> int _ 

println(parse(None)) // Failure(empty) 
println(parse(Some(""))) // Failure(empty) 
println(parse(Some("abc"))) // Failure(java.lang.NumberFormatException: For input string: "abc") 
println(parse(Some("42"))) // Success(42) 
Các vấn đề liên quan