2009-07-28 20 views
46

Option đơn nguyên là một cách biểu cảm tuyệt vời để đối phó với những thứ gì đó hoặc không có gì trong Scala. Nhưng điều gì sẽ xảy ra nếu người ta cần phải đăng nhập một tin nhắn khi "không có gì" xảy ra? Theo tài liệu Scala API,Sử dụng Hoặc để xử lý các lỗi trong mã Scala

Các Hoặc loại thường được sử dụng như một sự thay thế cho scala.Option nơi trái đại diện cho thất bại (theo quy ước) và phải là giống như một số.

Tuy nhiên, tôi không có may mắn để tìm các phương pháp hay nhất bằng cách sử dụng Ví dụ thực tế hoặc thực tế tốt liên quan đến việc xử lý lỗi. Cuối cùng tôi đã đi lên với đoạn mã sau cho dự án của riêng tôi:

def logs: Array[String] = { 
     def props: Option[Map[String, Any]] = configAdmin.map{ ca => 
      val config = ca.getConfiguration(PID, null) 
      config.properties getOrElse immutable.Map.empty 
     } 
     def checkType(any: Any): Option[Array[String]] = any match { 
      case a: Array[String] => Some(a) 
      case _ => None 
     } 
     def lookup: Either[(Symbol, String), Array[String]] = 
      for {val properties <- props.toRight('warning -> "ConfigurationAdmin service not bound").right 
       val logsParam <- properties.get("logs").toRight('debug -> "'logs' not defined in the configuration").right 
       val array <- checkType(logsParam).toRight('warning -> "unknown type of 'logs' confguration parameter").right} 
      yield array 

     lookup.fold(failure => { failure match { 
      case ('warning, msg) => log(LogService.WARNING, msg) 
      case ('debug, msg) => log(LogService.DEBUG, msg) 
      case _ => 
     }; new Array[String](0) }, success => success) 
    } 

(Xin lưu ý đây là một đoạn trích từ một dự án thực tế, vì vậy nó sẽ không biên dịch ngày của riêng mình)

Tôi muốn hãy biết ơn cách bạn đang sử dụng Either trong mã của bạn và/hoặc các ý tưởng tốt hơn về việc tái cấu trúc mã ở trên.

+1

Tôi có thể tìm thấy không đề cập đến bất cứ điều gì trong cuốn sách của Odersky. – skaffman

+4

Có, tôi có "Lập trình trong Scala" và không thể tìm thấy bất kỳ đề cập đến Hoặc trong đó. Sự tương tự tốt nhất tôi biết là Box trong Liftweb được sử dụng cho mục đích mang theo thất bại - nó giống như Option, nhưng với chức năng bổ sung. –

+0

Bất kỳ lựa chọn nào tốt hơn cho 'Tùy chọn [Hoặc [Foo, Bar]]'? – Jus12

Trả lời

44

Hoặc được sử dụng để trả lại một trong hai kết quả có thể có ý nghĩa, không giống như Tùy chọn được sử dụng để trả về một kết quả có ý nghĩa hoặc không có gì.

Một dễ hiểu ví dụ được đưa ra dưới đây (lưu thông trên Scala danh sách gửi thư một khi trở lại):

def throwableToLeft[T](block: => T): Either[java.lang.Throwable, T] = 
    try { 
    Right(block) 
    } catch { 
    case ex => Left(ex) 
    } 

Như tên hàm ngụ ý, nếu việc thực hiện các "khối" là thành công, nó sẽ trở lại "Phải (< kết quả>)". Nếu không, nếu Throwable được ném, nó sẽ trả về "Left (< throwable>)". Sử dụng đối sánh mẫu để xử lý kết quả:

var s = "hello" 
throwableToLeft { s.toUpperCase } match { 
    case Right(s) => println(s) 
    case Left(e) => e.printStackTrace 
} 
// prints "HELLO" 

s = null 
throwableToLeft { s.toUpperCase } match { 
    case Right(s) => println(s) 
    case Left(e) => e.printStackTrace 
} 
// prints NullPointerException stack trace 

Hy vọng điều đó sẽ hữu ích.

+5

Đặc biệt ... tại sao không chỉ ném ngoại lệ? – skaffman

+10

Có mã xử lý ngoại lệ trên toàn bộ địa điểm là xấu và khó quản lý. Sử dụng throwableToLeft biến xử lý ngoại lệ thành khớp mẫu, imho, dễ đọc và duy trì hơn. Ví dụ: –

+24

Ví dụ: bạn có thể có một số tác nhân thực hiện các phép tính khác nhau đồng thời một số trong số đó thực sự trả lại kết quả và một số loại bỏ một ngoại lệ. Nếu bạn chỉ ném ngoại lệ, một số diễn viên có thể chưa bắt đầu làm việc, bạn sẽ mất kết quả từ bất kỳ diễn viên nào chưa hoàn thành, v.v. Với cách tiếp cận này, tất cả các diễn viên sẽ trả về một giá trị (một số 'Trái', một số 'Right') và nó kết thúc được dễ dàng hơn để xử lý. –

6

Đoạn trích bạn đã đăng có vẻ rất giả tạo. Bạn sử dụng Hoặc trong trường hợp:

  1. Không đủ để biết dữ liệu không khả dụng.
  2. Bạn cần trả lại một trong hai loại riêng biệt.

Chuyển một ngoại lệ sang Trái là một trường hợp sử dụng phổ biến. Qua thử/nắm bắt, nó có lợi thế là giữ mã lại với nhau, điều này có ý nghĩa nếu ngoại lệ là một kết quả mong đợi là. Cách phổ biến nhất của xử lý Dù là mô hình phù hợp:

result match { 
    case Right(res) => ... 
    case Left(res) => ... 
} 

Một cách thú vị xử lý Either là khi nó xuất hiện trong một bộ sưu tập. Khi thực hiện một bản đồ trên một bộ sưu tập, việc ném một ngoại lệ có thể không khả thi, và bạn có thể muốn trả lại một số thông tin khác ngoài "không thể". Sử dụng một Hoặc cho phép bạn làm điều đó mà không quá tải các thuật toán:

val list = (
    library 
    \\ "books" 
    map (book => 
    if (book \ "author" isEmpty) 
     Left(book) 
    else 
     Right((book \ "author" toList) map (_ text)) 
) 
) 

Ở đây chúng ta có được một danh sách của tất cả các tác giả trong thư viện, cộng một danh sách các cuốn sách mà không cần một tác giả.Vì vậy, chúng tôi có thể xử lý thêm cho phù hợp:

val authorCount = (
    (Map[String,Int]() /: (list filter (_ isRight) map (_.right.get))) 
    ((map, author) => map + (author -> (map.getOrElse(author, 0) + 1))) 
    toList 
) 
val problemBooks = list flatMap (_.left.toSeq) // thanks to Azarov for this variation 

Vì vậy, cơ bản Hoặc việc sử dụng sẽ diễn ra như vậy. Nó không phải là một lớp học đặc biệt hữu ích, nhưng nếu nó đã được bạn đã thấy nó trước đây. Mặt khác, nó cũng không vô ích.

+0

danh sách flatMap {_.left.toSeq} dường như trả về cùng một problemBooks, đúng không? –

+0

Vâng, nó sẽ. Tôi biết có một mẹo phẳng MapMap, nhưng tôi không thể tìm thấy nó khi tôi viết ví dụ. –

12

Thư viện Scalaz có thứ gì đó tương tự Hoặc được đặt tên là Xác thực. Nó là thành ngữ hơn hoặc là để sử dụng như là "có được một kết quả hợp lệ hoặc một thất bại".

Xác thực cũng cho phép tích lũy lỗi.

Chỉnh sửa: "giống nhau" Giả mạo là sai, bởi vì Xác thực là hàm functor và scalaz Hoặc, có tên \/(phát âm là "disjonction" hoặc "một trong hai"), là một đơn nguyên. Thực tế là xác thực có thể tích lũy lỗi là do tính chất đó. Mặt khác,/có một bản chất "dừng sớm", dừng lại ở đầu tiên - \/(đọc nó "trái", hoặc "lỗi") nó gặp phải. Có một lời giải thích hoàn hảo ở đây: http://typelevel.org/blog/2014/02/21/error-handling.html

Xem: http://scalaz.googlecode.com/svn/continuous/latest/browse.sxr/scalaz/example/ExampleValidation.scala.html

Theo yêu cầu bình luận, sao chép/dán các liên kết ở trên (một số dòng bị loại bỏ):

// Extracting success or failure values 
val s: Validation[String, Int] = 1.success 
val f: Validation[String, Int] = "error".fail 

// It is recommended to use fold rather than pattern matching: 
val result: String = s.fold(e => "got error: " + e, s => "got success: " + s.toString) 

s match { 
    case Success(a) => "success" 
    case Failure(e) => "fail" 
} 

// Validation is a Monad, and can be used in for comprehensions. 
val k1 = for { 
    i <- s 
    j <- s 
} yield i + j 
k1.toOption assert_≟ Some(2) 

// The first failing sub-computation fails the entire computation. 
val k2 = for { 
    i <- f 
    j <- f 
} yield i + j 
k2.fail.toOption assert_≟ Some("error") 

// Validation is also an Applicative Functor, if the type of the error side of the validation is a Semigroup. 
// A number of computations are tried. If the all success, a function can combine them into a Success. If any 
// of them fails, the individual errors are accumulated. 

// Use the NonEmptyList semigroup to accumulate errors using the Validation Applicative Functor. 
val k4 = (fNel <**> fNel){ _ + _ } 
k4.fail.toOption assert_≟ some(nel1("error", "error")) 
+2

Thật tuyệt khi thấy một ví dụ ở đây trong câu trả lời. Áp dụng cho loại vấn đề được nêu ở đây trong câu hỏi. –

+0

'flatMap', và do đó để hiểu về' Validation' đã không được chấp nhận trong Scalaz 7.1 và được gỡ bỏ trong Scalaz 7.1. Các ví dụ trong câu trả lời không còn hoạt động nữa. Xem [thảo luận không dùng nữa] (https://groups.google.com/forum/#!topic/scalaz/Wnkdyhebo2w) – kostja