2012-01-20 31 views
7

Tôi rất mới với Scala và tôi vẫn đang cố làm quen với cú pháp và kiểu, vì vậy đây có lẽ là một câu hỏi rất đơn giản.Làm cách nào để tách các Lớp Trường hợp có các Tùy chọn trong Scala

Tôi đang làm việc với một codebase nơi có rất nhiều trường hợp các lớp dân cư với tùy chọn như sau:

case class Person(
    pants: Option[Pants] 
) 
case class Pants(
    pocket: Option[Pocket] 
) 
case class Pocket(
    cash: Option[Cash] 
) 
case class Cash(
    value: String = "zilch" 
) 

Trong ví dụ trên, làm thế nào bạn sẽ đi về trở về bao nhiêu tiền là trong một Person ' s PantsPocket, nếu họ thực sự mặc quần ... với túi, và nếu họ có tiền nào cả?

Trả lời

8

Một thời gian tuyệt vời cho for-comprehensions:

val someCash: Option[Cash] = 
    for(pants <- somePerson.pants; 
     pocket <- pants.pocket; 
     cash <- pocket.cash) yield cash 

Tương bạn có thể viết những điều sau đây, trong đó các mã đầu tiên là đường cú pháp (bỏ qua một số sự tinh tế):

val someCash: Option[Cash] = 
    somePerson.pants.flatMap(_.pocket.flatMap(_.cash)) 

(Tôi không hoàn toàn chắc chắn nếu bạn có thể viết biểu thức cuối cùng bằng cách sử dụng các ký tự đại diện _, như tôi đã làm). câu trả lời

+0

Tuyệt vời, cảm ơn! Cách tiếp cận hiểu được thực sự là chính xác những gì tôi đang cố gắng làm, nhưng cấu trúc tôi đang làm việc không sạch như ví dụ tôi đã đưa ra ở trên. Ít nhất điều này xác nhận tôi đang đi đúng hướng. –

2

ziggystar là những gì tôi sẽ sử dụng, nhưng cho đầy đủ, phù hợp với mô hình cũng có thể được sử dụng, ví dụ,

val someCash: Option[Cash] = person match { 
    case Person(Some(Pants(Some(Pocket(Some(cash)))))) => Some(cash) 
    case _ => None 
} 
6

Câu hỏi đặt ra đã không đề cập sửa đổi dữ liệu, nhưng khi bạn cần làm điều này bạn nhanh chóng tìm thấy thư viện Scala không có các công cụ để thực hiện điều này dễ dàng (khi dữ liệu là không thay đổi). Nếu bạn chưa trải nghiệm điều này, hãy thử viết một hàm sẽ thay thế hoặc sửa đổi, value của Cash được giữ bởi một Person, sử dụng các loại được xác định trong câu hỏi.

Như được mô tả trong ống kính Tony Morris 'Asymmetric Lenses in Scala, là giải pháp thích hợp cho vấn đề này.

Dưới đây là một ví dụ về cách chúng ta có thể truy cập và cập nhật các value của Cash sử dụng LensPLens (ống kính một phần) triển khai của một người từ scalaz-seven chi nhánh của Scalaz.

Đầu tiên, một số bản mẫu: xác định phiên bản Lens cho từng trường của các lớp chữ hoa chữ thường. A @[email protected] B có nghĩa là Lens[A, B].

val pants: Person @[email protected] Option[Pants] = 
    lensG(_.pants, p => ps => p.copy(pants = ps)) 

val pocket: Pants @[email protected] Option[Pocket] = 
    lensG(_.pocket, ps => p => ps.copy(pocket = p)) 

val cash: Pocket @[email protected] Option[Cash] = 
    lensG(_.cash, p => c => p.copy(cash = c)) 

val value: Cash @[email protected] String = 
    lensG(_.value, c => v => c.copy(value = v)) 

Tuy nhiên, chúng tôi không thể tạo tất cả các ống kính này vì hầu hết các trường được bao bọc trong các loại Option.

Ống kính phần để giải cứu: những cho phép chúng ta truy cập và cập nhật các phần của một cấu trúc mà có thể không tồn tại, chẳng hạn như giá trị của một SomeOption, hoặc head của một List.

Chúng tôi có thể sử dụng chức năng somePLens từ Scalaz 7 để tạo ống kính một phần xem từng trường tùy chọn.Tuy nhiên, để tạo một thấu kính một phần với một trong các ống kính thông thường của chúng tôi, chúng tôi cần truy cập vào trường hợp ống kính một phần tương đương cho ống kính thông thường, sử dụng phương pháp partial tồn tại trên mọi Lens.

// @-? is an infix type alias for PLens 
val someCash: Pocket @-? Cash = cash.partial andThen somePLens 

scala> someCash.get(Pocket(Some(Cash("zilch")))) 
res1: Option[Cash] = Some(Cash(zilch)) 

Trong cùng một cách, chúng ta có thể tạo ra ống kính một phần của chúng tôi xem tiền mặt nắm giữ bởi một Person bằng cách soạn bài partial trường hợp ống kính của chúng tôi, và kẹp trường hợp của somePLens. Ở đây, tôi đã sử dụng toán tử <=<, một bí danh cho andThen (tương đương với compose với các toán hạng được bật).

val someCashValue: Person @-? String = 
    pants.partial <=< somePLens <=< 
    pocket.partial <=< somePLens <=< 
    cash.partial <=< somePLens <=< 
    value.partial 

Tạo một trường hợp Person để chơi với:

val ben = Person(Some(Pants(Some(Pocket(Some(Cash("zilch"))))))) 

Sử dụng ống kính một phần để truy cập giá trị tiền mặt tôi có:

scala> someCashValue.get(ben) 
res2: Option[String] = Some(zilch) 

Sử dụng ống kính một phần để thay đổi giá trị :

scala> someCashValue.mod(_ + ", zero, nada", ben) 
res3: Person = Person(Some(Pants(Some(Pocket(Some(Cash(zilch, zero, nada))))))) 
(!) 0

Bây giờ, nếu tôi không mặc bất kỳ quần, chúng ta có thể xem như thế nào là một nỗ lực để thay đổi giá trị tiền mặt của tôi sẽ không có hiệu lực:

scala> val ben = Person(None) 
ben: Person = Person(None) 

scala> someCashValue.mod(_ + ", zero, nada", ben) 
res4: Person = Person(None) 
12

Scalaz 7 đã thay đổi một chút để đây là một ví dụ khác:

object PartialLensExample extends App { 

    import scalaz._ 
    import Lens._ 
    import PLens._ 


    case class Bar(blub: Option[String]) 
    case class Foo(bar: Option[Bar]) 

    // normal lenses for getting and setting values 
    val fooBarL: Foo @> Option[Bar] = lensg(foo ⇒ bar ⇒ foo.copy(bar = bar), _.bar) 
    val barBlubL: Bar @> Option[String] = lensg(bar ⇒ blub ⇒ bar.copy(blub = blub), _.blub) 

    // compose the above as 'Partial Lenses', >=> is just an alias for 'andThen' 
    val fooBarBlubL: Foo @?> String = fooBarL.partial >=> somePLens >=> barBlubL.partial >=> somePLens 

    // try it 
    val foo = Foo(Some(Bar(Some("Hi")))) 

    println(fooBarBlubL.get(foo)) // Some(Hi) 

    println(fooBarBlubL.set(foo, "Bye")) //Foo(Some(Bar(Some(Bye)))) 

    // setting values 
    val foo2 = Foo(None) 
    println(fooBarL.set(foo2, Some(Bar(None)))) // Foo(Some(Bar(None))) 

} 
+1

Câu trả lời tuyệt vời, nhưng một trở ngại lớn ở đây khi thiết lập các giá trị lồng nhau là: nếu bất kỳ giá trị nào là 'None' thì sao? 'PLens' trong ví dụ của bạn sẽ chỉ cho phép' blub: Option [String] 'được gán nếu' blub' _already có 'Some' value_. Tôi đã tìm thấy quá trình chuyển đổi trạng thái monadic có thể khởi tạo thành viên cấp cao nhất, nhưng tôi không rõ làm thế nào để được thực hiện cho những người tiếp tục xuống. –

+1

hmm, vâng. Tôi cập nhật câu trả lời cho việc thiết lập một Bar trên một Foo, nhưng tôi thấy vấn đề thiết lập giá trị blub trong Bar - tôi sẽ cần phải suy nghĩ về điều đó. –

+0

[Sự bogosity của quá trình chuyển đổi trạng thái bằng cách sử dụng 'PLens' trường hợp] (https://gist.github.com/michaelahlers/20ec194410f89422847fdd3a71777c69) là tốt nhất tôi có thể làm, khởi tạo tài sản khi cần thiết (có ý nghĩa, đặc biệt là trong ánh sáng của câu trả lời liên quan đến quần] (http://stackoverflow.com/a/9978488/700420) @ ben-james đã cho). Điều này khiến tôi quá mệt mỏi và nó không quy mô, vì vậy tôi tưởng tượng có một cách tiếp cận tốt hơn. –

Các vấn đề liên quan