Nhận giá trị Tùy chọn hoặc ném một ngoại lệ


77

Với một Option, cách thành ngữ để nhận được giá trị của nó hoặc cố gắng ném một ngoại lệ là gì?

def foo() : String = {
  val x : Option[String] = ...
  x.getOrException()
}

12
Tại sao không .get?
Mysterious Dan

@MyseriousDan Tôi vừa tình cờ gặp trường hợp này. Tôi có một tình huống mà tôi hoàn toàn chắc chắn 100% nó phải trả về Some, và nếu không, đó là một vấn đề nghiêm trọng và phải ném. Nhưng tôi muốn loại bỏ ngoại lệ của riêng mình và bất kỳ ngoại lệ nào không rõ ràng NoSuchElementException. Ngoài ra, hầu hết thời gian phương pháp này có thể trả về Không một cách an toàn.
Kai Sellgren

1
Câu hỏi thực sự là tại sao loại của bạn nói rằng nó có thể bị thiếu khi bạn biết rằng nó không phải. Nếu bạn có thể thể hiện kiến ​​thức của mình bằng các loại, thì điều đó nói chung là tốt nhất. Nó có thể liên quan đến việc định hình lại các lớp của bạn một chút, nhưng nhìn chung sẽ dễ chịu hơn rất nhiều về lâu dài.
Mysterious Dan

1
Câu trả lời của @TravisBrown nên được chấp nhận, không phải của tôi. Của tôi không phải là thành ngữ, tôi viết nó khi tôi vẫn còn là một "newbie" (tôi vẫn vậy, nhưng cấp 2). Hãy để nó đứng như một ví dụ những gì không để làm ... ;-)
Sebastian N.

Bạn đang làm sai nếu bạn phải ném một ngoại lệ. Cách thành ngữ để sử dụng Option là trả về Không có và không ném một ngoại lệ nào cả.
Priyank Desai

Câu trả lời:


46

(CHỈNH SỬA: đây không phải là cách tốt nhất hoặc thành ngữ nhất để làm điều đó. Tôi đã viết nó khi tôi chưa quen với Scala. Tôi để nó ở đây để làm ví dụ về cách không làm điều đó. Ngày nay tôi sẽ làm với tên @TravisBrown)

Tôi nghĩ rằng nó thực sự tóm gọn lại hai điều:

  • làm thế nào bạn chắc chắn rằng giá trị là ở đó?
  • bạn muốn phản ứng như thế nào nếu không?

Nếu tại thời điểm đó trong mã của bạn, bạn mong đợi giá trị ở đó và trong trường hợp từ xa, đó không phải là bạn muốn chương trình của mình bị lỗi nhanh, thì tôi sẽ chỉ làm bình thường getvà để Scala ném NoSuchElementExceptionnếu không có giá trị :

def foo (): String = {
  val x: Tùy chọn [Chuỗi] = ...
  x.get
}

Nếu bạn muốn xử lý trường hợp theo cách khác (hãy đưa ra ngoại lệ của riêng bạn), tôi nghĩ một cách thanh lịch hơn sẽ trông như thế này:

def foo (): String = {
  val x: Option [String] = Không có
  x khớp với {
    trường hợp Some (giá trị) => giá trị
    case None => ném MyRuntimeException mới ("blah")
  }
} 

Và tất nhiên nếu bạn muốn cung cấp giá trị thay thế của riêng bạn cho các trường hợp đó OptionNonebạn sẽ chỉ cần sử dụng getOrElse:

def foo (): String = {
  val x: Option [String] = Không có
  x.getOrElse ("giá trị thay thế của tôi")
} 

Tất nhiên cần lưu ý rằng việc so khớp các giá trị tăng Một số (giá trị) chỉ để trích xuất cùng giá trị đó không phải là Scala thành ngữ và điều đó getOrElsesẽ được ưu tiên nếu đó là tất cả những gì người ta muốn làm. Tôi chỉ muốn minh họa trường hợp "nếu được xác định, đánh giá thành X, nếu không, ném ngoại lệ".
Sebastian N.

1
Để trích dẫn Guillaume Belrose : "Tôi đoán tôi vẫn còn trong giờ nghiệp dư sau đó :-)"
Sebastian N.

14
Bạn cũng có thể kết hợp các đề xuất của @ SebastianN. bằng cách đưa ra một ngoại lệ getOrElsenhư vậy:x.getOrElse(throw new MyRuntimeException("message"))
Jona

4
@TravisBrown: sau khi có thêm kinh nghiệm Scala, tôi phải nói rằng bạn đúng. Việc so khớp trên Option như thế này thay vì sử dụng getOrElse là không cần thiết và không phải là thành ngữ. Của bạn phải là câu trả lời được chấp nhận.
Sebastian N.

132

Một throw"câu lệnh" thực sự là một biểu thức trong Scala, và nó có kiểu Nothing, là kiểu con của mọi kiểu khác. Điều này có nghĩa là bạn chỉ có thể sử dụng cũ đơn giản getOrElse:

def myGet[A](oa: Option[A]) = oa.getOrElse(throw new RuntimeException("Can't."))

Tuy nhiên, bạn thực sự không nên làm điều này.


1
Tại sao không? Cách thanh lịch / thành ngữ hơn để làm điều này là gì? Đối sánh mẫu?
Sebastian N.

7
Bạn nhận được thêm rất nhiều dặm từ hệ thống loại nếu bạn mô hình hóa các lỗi thành các giá trị trong suốt chương trình của mình (và lưu các ngoại lệ cho các vấn đề thực sự đặc biệt — ví dụ OutOfMemoryError).
Travis Brown,

23
Tôi không coi các lỗi mô hình hóa lợi thế là giá trị cho bất kỳ vấn đề không thể khắc phục nào (trong đó rõ ràng là OOME). Ví dụ: đọc siêu dữ liệu từ tệp cấu hình. Việc thực hiện đúng chương trình yêu cầu một tệp cấu hình được định dạng tốt. Nếu tệp cấu hình chứa các mục nhập không hợp lệ do lỗi người dùng, thì chương trình không thể tiếp tục. Vì vậy, nó có vẻ phù hợp để mở ngăn xếp và rút lui trong các tình huống như vậy.
Jonathan Neufeld

1
@JonathanNeufeld Nhận xét từ Travis Brown nghe có vẻ giống như một cách tiếp cận thuần túy fp hơn. Nó không dành cho tất cả mọi người / mọi trường hợp sử dụng. Tôi quan tâm đến bạn hơn - chỉ cần bảo lãnh và không thúc đẩy quá nhiều việc xử lý hệ thống kiểu logic / ưa thích.
StephenBoesch

Tôi không nghĩ @TravisBrown là một người theo chủ nghĩa thuần túy, các câu trả lời khác của anh ấy trên SO cho thấy điều ngược lại. Tuy nhiên, việc ném hay mô hình thất bại thực sự là một câu hỏi về bối cảnh. Trong nhiều trường hợp, lỗi ứng dụng không phải là một tùy chọn (nhưng đối với lỗi không thể khôi phục như OOME). Nếu trường hợp này xảy ra, việc quản lý lỗi phải được mô hình hóa trong toàn bộ mã và có thể theo dõi bằng thiết kế.
Juh_

14

Chỉ cần sử dụng phương thức .get.

def get[T](o:Option[T]) = o.get

Nó sẽ ném ra một NoSuchElementException nếu o là một thể hiện của None.

Về cơ bản, tôi sẽ làm việc với các tùy chọn như sau:

def addPrint(oi:Option[Int]) = oi.map(_+1).foreach(println)
addPrint(Some(41))
addPrint(Some(1336))
addPrint(None)

để tránh câu hỏi cụ thể của bạn.


4
Tôi đã giả định rằng OP muốn kiểm soát nhiều hơn đối với ngoại lệ được ném ra, nhưng ngay cả khi không, đó getlà một ý kiến ​​tồi — nó chỉ phản ánh thực tế là bạn đang làm điều gì đó khó chịu. Việc sử dụng getOrElsevà ném một cách rõ ràng một ngoại lệ sẽ khó bỏ lỡ hơn khi bạn quyết định đây không chỉ là mã một lần và muốn làm cho nó an toàn hơn.
Travis Brown

Tất nhiên, về cơ bản đối với tôi, có vẻ như bạn không bao giờ nên sử dụng bất kỳ loại tùy chọn nào trừ khi bạn thực sự phải làm vậy. Tôi sẽ sử dụng foreach hoặc map để tính toán với các tùy chọn.
Felix

11

Tôi hy vọng điều này sẽ giúp bạn hiểu cách biểu diễn lỗi (và nói chung là hiệu ứng) bằng cách sử dụng các loại.

Các chiến lược xử lý lỗi trong Scala chức năng

  • Sử dụng Optionđể trả về các giá trị tùy chọn. Ví dụ - không tìm thấy thực thể trong bộ nhớ.

  • Sử dụng Option(possiblyNull)để tránh các trường hợp Some(null).

  • Sử dụng Either[Error, T]để báo cáo lỗi dự kiến. Ví dụ - định dạng email sai, không thể phân tích cú pháp chuỗi thành số, v.v.

  • Lập mô hình lỗi của bạn dưới dạng ADT (nói đơn giản là loại cấu trúc phân cấp loại) để sử dụng nó, ví dụ, ở Bên trái của Một trong hai để biểu diễn các tình huống lỗi phức tạp hơn.

  • Ném Exceptionchỉ để báo hiệu những hỏng hóc không mong muốn và không thể khôi phục. Như thiếu tệp cấu hình.

  • Sử dụng Either.catchOnlyhoặc Tryhoặc Cats.IO(nâng cao) thay vì một khối bắt để xử lý các lỗi không mong muốn. Gợi ý: Bạn vẫn có thể sử dụng ADT nhưng mở rộng chúng từ các vật có thể ném. Thông tin thêm về EithervsTry .

  • Sử dụng kiểu Validateddữ liệu từ Cats lib để tích lũy lỗi thay vì fail-fast ( Either), nhưng thích Either ở cấp mô-đun để đơn giản hóa thành phần của chương trình (để có các kiểu giống nhau). Ví dụ - xác thực dữ liệu biểu mẫu, tích lũy lỗi phân tích cú pháp.

  • Sử dụng các loại đã đề cập và không tối ưu hóa chương trình trước - vì hầu hết có thể, các kiểu cổ chai sẽ nằm trong logic kinh doanh, không phải trong các loại hiệu ứng.

Cách tiếp cận như vậy sẽ đơn giản hóa việc bảo trì và cập nhật mã của bạn vì bạn có thể lý luận về nó mà không cần đi đến chi tiết triển khai cụ thể (hay còn gọi là lý luận cục bộ). Ngoài ra - giảm lỗi - bạn không thể bỏ lỡ một lỗi trong loại. Và soạn chương trình dễ dàng hơn (với sự giúp đỡ của map, flatMapvà combinators khác) - vì nó đơn giản về mức loại, chứ không phải là trường hợp ngoại lệ với phi địa phương và tác dụng phụ.
Tìm hiểu thêm về chức năng Scala.


Nhưng hãy lưu ý rằng đôi khi với cách tiếp cận này, các kiểu tiếp cận có thể chồng chất lên nhau và việc soạn thảo mọi thứ có thể trở nên khó khăn hơn. Ví dụ: x: Future[Either[Error, Option[T]]]Bạn có thể làm gì:

  • Sử dụng mapflatMapkết hợp với so khớp mẫu để tạo các giá trị khác nhau của các loại như vậy, ví dụ:
x.faltMap { case Right(Some(v)) => anotherFuture(v); case Left(er) => ... }
  • Nếu nó không hiệu quả, bạn có thể thử sử dụng MonadTransformers (đừng sợ cái tên này, nó chỉ bao bọc xung quanh các loại hiệu ứng như Either và Future)
  • Ngoài ra, một tùy chọn là đơn giản hóa các lỗi của bạn ADT bằng cách mở rộng chúng khỏi Có thể ném để thống nhất nó với Tương lai, sau đó nó sẽFuture[Option[T]]


Và cuối cùng, trong trường hợp của bạn, một lựa chọn sẽ là:

def foo() : Either[Error, String] = {
    val x : Option[String] = ...
    x match {
        case Some(v) => Right(v)
        case None => Left(Error(reason))
    }
}

Bản tóm tắt khá đẹp. Bạn rời đi cuối cùng "nghĩ" vào giữa câu;)
StephenBoesch

@YordanGeorgiev liên kết là thối, loại bỏ
Albert Bikeev

1

Scala hiện hỗ trợ thao tác này trên bản đồ bằng getOrElse()phương pháp, xem tài liệu tại đây

Như đã chỉ ra, ném một ngoại lệ trong Scala cũng là một biểu thức.

Vì vậy, bạn có thể làm như sau:

myMap.getOrElse(myKey, throw new MyCustomException("Custom Message HERE")
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.