2011-11-16 26 views
9

Tôi có một List [Option [Int]] và muốn tổng hợp nó bằng cách sử dụng functors ứng dụng. Từ [1] Tôi hiểu rằng nó phải là một cái gì đó như sauTổng hợp một danh sách các tùy chọn với Functors ứng dụng

import scalaz._ 
import Scalaz._ 

List(1,2,3).map(some(_)).foldLeft(some(0))({ 
    case (acc,value) => (acc <|*|> value){_+_} 
}) 

tuy nhiên tôi chỉ đơn giản là không thể tìm ra cách chính xác để viết những dòng này. Tôi sẽ rất vui nếu ai đó có thể giúp tôi với điều này.

Thank you very much

[1] How to combine Option values in Scala?

Sửa

Cảm ơn tất cả các câu trả lời tuyệt vời.

Nếu có bất kỳ Không có gì trong danh sách, tôi muốn nó trả về Không. Tôi đang cố gắng thay thế Null/Exception bằng Option/Either và xem liệu tôi có thể tạo ra một số mã có thể sử dụng được không.

Một số chức năng sẽ điền vào danh sách của tôi và tôi muốn xử lý nó một cách dễ dàng nhất có thể mà không kiểm tra xem một trong các thành phần có là Không. Nó sẽ hoạt động tương tự như Exceptions, nơi mà tôi không phải kiểm tra nó trong hàm của mình, nhưng hãy để người gọi xử lý nó.

+0

Hi Manuel! Có phần quan trọng luôn là cách xử lý Không: Bỏ qua hoặc Thất bại nhanh chóng xem tại đây cho một ví dụ liên quan: https://gist.github.com/970717 – AndreasScheinert

+0

Hi Andreas. Đoạn mã như của bạn là những gì tôi cần. –

Trả lời

9

Nếu bạn có Option[T] và nếu có một Monoid cho T, sau đó có một Monoid[Option[T]]:

implicit def optionTIsMonoid[T : Monoid]: Monoid[Option[T]] = new Monoid[Option[T]] { 
    val monoid = implicitly[Monoid[T]] 
    val zero = None 
    def append(o1: Option[T], o2: =>Option[T]) = (o1, o2) match { 
    case (Some(a), Some(b)) => Some(monoid.append(a, b)) 
    case (Some(a), _)  => o1 
    case (_, Some(b))  => o2 
    case _     => zero 
    } 
} 

Khi bạn được trang bị này, bạn có thể chỉ sử dụng sum (tốt hơn foldMap(identity), như được đề xuất bởi @missingfaktor):

List(Some(1), None, Some(2), Some(3), None).asMA.sum === Some(6) 

CẬP NHẬT

Chúng tôi thực sự có thể sử dụng applicatives để đơn giản hóa các mã trên:

implicit def optionTIsMonoid[T : Monoid]: Monoid[Option[T]] = new Monoid[Option[T]] { 
    val monoid = implicitly[Monoid[T]] 
    val zero = None 
    def append(o1: Option[T], o2: =>Option[T]) = (o1 |@| o2)(monoid.append(_, _)) 
} 

mà làm cho tôi nghĩ rằng chúng ta có lẽ thậm chí có thể khái quát hơn nữa để:

implicit def applicativeOfMonoidIsMonoid[F[_] : Applicative, T : Monoid]: Monoid[F[T]] = 
    new Monoid[F[T]] { 
    val applic = implicitly[Applicative[F]] 
    val monoid = implicitly[Monoid[T]] 

    val zero = applic.point(monoid.zero) 
    def append(o1: F[T], o2: =>F[T]) = (o1 |@| o2)(monoid.append(_, _)) 
    } 

Giống như rằng bạn thậm chí có thể tổng hợp Danh sách Danh sách, Danh sách Cây, ...

UPDATE2

Câu hỏi đặt ra rõ khiến tôi nhận ra rằng CẬP NHẬT trên là không chính xác!

Trước hết optionTIsMonoid, như refactored, không tương đương với định nghĩa đầu tiên, kể từ khi định nghĩa đầu tiên sẽ bỏ None giá trị trong khi thứ hai sẽ trở lại None càng sớm càng có một None trong danh sách đầu vào. Nhưng trong trường hợp đó, đây không phải là Monoid! Thật vậy, Monoid[T] phải tôn trọng luật Monoid và zero phải là một phần tử identity.

Chúng ta nên có:

zero |+| Some(a) = Some(a) 
Some(a) |+| zero = Some(a) 

Nhưng khi tôi đề xuất định nghĩa cho Monoid[Option[T]] sử dụng Applicative cho Option, đây không phải là trường hợp:

None |+| Some(a) = None 
None |+| None = None 
=> zero |+| a  != a 

Some(a) |+| None = zero 
None |+| None = zero 
=> a |+| zero != a 

Việc sửa chữa không phải là khó khăn, chúng tôi cần phải thay đổi định nghĩa của zero:

// the definition is renamed for clarity 
implicit def optionTIsFailFastMonoid[T : Monoid]: Monoid[Option[T]] = 
    new Monoid[Option[T]] { 
    monoid = implicitly[Monoid[T]] 
    val zero = Some(monoid.zero) 
    append(o1: Option[T], o2: =>Option[T]) = (o1 |@| o2)(monoid.append(_, _)) 
    } 

Trong trường hợp này chúng ta sẽ có (với T như Int):

Some(0) |+| Some(i) = Some(i) 
Some(0) |+| None = None 
=> zero |+| a  = a 

Some(i) |+| Some(0) = Some(i) 
None |+| Some(0) = None 
=> a |+| zero = zero 

nào chứng tỏ rằng luật bản sắc được xác minh (chúng ta cũng nên xác minh rằng luật associative được tôn trọng, ...).

Bây giờ chúng tôi có 2 Monoid[Option[T]] mà chúng tôi có thể sử dụng theo ý muốn, tùy thuộc vào hành vi chúng tôi muốn khi tổng hợp danh sách: bỏ qua None s hoặc "không nhanh".

+0

Chỉ sau khi đăng bài này, tôi nhận ra rằng tôi không thực sự trả lời câu hỏi, vì tôi không sử dụng bất kỳ Đơn. Chỉ cần xem xét đây là một trong rất nhiều lựa chọn thay thế, ... – Eric

+0

Tôi không hiểu cách thức hoạt động ... tại sao không có '+' ở đâu? – Owen

+1

'.foldMap (identity)' có thể được thay thế bằng '.asMA.sum'. – missingfaktor

5

Một lựa chọn sẽ được thiết lập trình tự toàn bộ danh sách đầu tiên, sau đó gấp nó như bình thường:

val a: List[Option[Int]] = List(1, 2, 3) map (Some(_)) 
a.sequence map (_.foldLeft(0)(_+_)) 
+2

Hoặc, thực sự, chỉ là bản đồ a.sequence {_.sum} – Submonoid

14

Bạn không thực sự cần Scalaz cho việc này. Bạn chỉ có thể flatten danh sách, mà sẽ chuyển đổi nó thành List[Int], loại bỏ bất kỳ mục nào là None. Sau đó, bạn có thể giảm bớt nó:

List(Some(1), None, Some(2), Some(3), None).flatten.reduce(_ + _) //returns 6: Int 
+0

Tôi đã giải thích nó là muốn tạo kết quả 'None' nếu có bất kỳ' None', nhưng bây giờ bạn đề cập đến, tôi không chắc là mình bên phải ... – Owen

+0

Điểm rất tốt, tôi cho rằng OP nên xác định liệu anh ta có muốn tổng số thất bại nếu ít nhất một mục là Không, hoặc tổng hợp các mục có giá trị, bỏ qua các Nones. –

+6

Hoặc '.flatten.sum'. – missingfaktor

6
scala> List(1, 2, 3).map(some).foldLeft(0 some) { 
    | case (r, c) => (r |@| c)(_ + _) 
    | } 
res180: Option[Int] = Some(6) 
0

Với ứng dụng ScalazBuilder sẽ là một tùy chọn khác.

import scalaz._ 
import Scalaz._ 

List(1,2,3).map(_.some).foldl1((acc,v) => (acc |@| v) {_+_}) join 
0

Các mặt hàng này ở đâu đó một thời gian trước, không thể tìm thấy nguồn nữa, nhưng nó đã làm việc cho tôi

def sumOpt1(lst: List[Option[Int]]): Option[Int] = { 
    lst.foldLeft(Option.empty[Int]) { 
     case (prev, elem) => 
     (prev, elem) match { 
      case (None, None) => None 
      case (None, Some(el)) => Some(el) 
      case (Some(p), None) => Some(p) 
      case (Some(p), Some(el)) => Some(p + el) 
     } 
    } 
    } 

hoặc

def sumOpt2(lst: List[Option[Int]]): Option[Int] = { 
    lst.foldLeft(Option.empty[Int]) { 
     case (prev, elem) => 
     (prev, elem) match { 
      case (None, None) => None 
      case (p, el) => Some(p.getOrElse(0) + el.getOrElse(0)) 
     } 
    } 
    } 

hoặc

def sumOpt3(lst: List[Option[Int]]): Option[Int] = { 
    lst.foldLeft(Option.empty[Int]) { 
     case (prev, elem) => 
     (prev, elem) match { 
      case (None, el) => el 
      case (p, None) => p 
      case (Some(p), Some(el)) => Some(p + el) 
     } 
    } 
    } 
Các vấn đề liên quan