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".
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
Hi Andreas. Đoạn mã như của bạn là những gì tôi cần. –