To kontynuacja mojego poprzedniego pytanie. Napisałem monadę (dla ćwiczenia), która w rzeczywistości jest funkcją generującą losowe wartości. Jednak nie jest zdefiniowane jako instancja klasy typu scalaz.Monad
.
Teraz spojrzałem na Rng biblioteka i zauważyłem, że to zdefiniowało Rng
tak jak scalaz.Monad
:
implicit val RngMonad: Monad[Rng] =
new Monad[Rng] {
def bind[A, B](a: Rng[A])(f: A => Rng[B]) = a flatMap f
def point[A](a: => A) = insert(a)
}
Zastanawiam się więc, jak dokładnie użytkownicy z tego korzystają. Jak możemy wykorzystać fakt, że Rng
jest instancją klasy typu scalaz.Monad
? Czy możesz podać jakieś przykłady?
Odpowiedzi:
3 dla odpowiedzi № 1Jedną z korzyści jest to, że otrzymasz wiele przydatnych metod zdefiniowanych w MonadOps
.
Na przykład, Rng.double.iterateUntil(_ < 0.1)
da tylko wartości mniejsze niż 0.1
(podczas gdy wartości większe niż 0.1
zostanie pominięty).
iterateUntil
może być wykorzystywany do generowania próbek dystrybucji przy użyciu metody odrzucania. Na przykład. to jest kod, który tworzy przykładowy generator dystrybucji beta:
import com.nicta.rng.Rng
import java.lang.Math
import scalaz.syntax.monad._
object Main extends App {
def beta(alpha: Double, beta: Double): Rng[Double] = {
// Purely functional port of Numpy"s beta generator: https://github.com/numpy/numpy/blob/31b94e85a99db998bd6156d2b800386973fef3e1/numpy/random/mtrand/distributions.c#L187
if (alpha <= 1.0 && beta <= 1.0) {
val rng: Rng[Double] = Rng.double
val xy: Rng[(Double, Double)] = for {
u <- rng
v <- rng
} yield (Math.pow(u, 1 / alpha), Math.pow(v, 1 / beta))
xy.iterateUntil { case (x, y) => x + y <= 1.0 }.map { case (x, y) => x / (x + y) }
} else ???
}
val rng: Rng[List[Double]] = beta(0.5, 0.5).fill(10)
println(rng.run.unsafePerformIO) // Prints 10 samples of the beta distribution
}
7 dla odpowiedzi nr 2
Oto prosty przykład. Załóżmy, że chcę wybrać losowy rozmiar dla zakresu, a następnie wybrać losowy indeks w tym zakresie, a następnie zwrócić zarówno zakres, jak i indeks. Drugie obliczenie wartości losowej wyraźnie zależy od pierwszego - muszę znać rozmiar zakresu, aby wybrać wartość z tego zakresu.
Właśnie tego służy wiązanie monadyczne - pozwala napisać następujące rzeczy:
val rangeAndIndex: Rng[(Range, Int)] = for {
max <- Rng.positiveint
index <- Rng.chooseint(0, max)
} yield (0 to max, index)
Nie byłoby to możliwe, gdybyśmy nie mieli Monad
instancja dla Rng
.
2 dla odpowiedzi nr 3
Jak każdy interfejs, deklarując wystąpienie Monad[Rng]
robi dwie rzeczy: zapewnia implementację Monad
metody pod standardowymi nazwami i wyraża niejawną umowę, że te implementacje metod są zgodne z niektórymi przepisami (w tym przypadku przepisami dotyczącymi monady).
@ Travis podał przykład jednej rzeczy zaimplementowanej z tymi interfejsami, implementacji Scalaz map
i flatMap
. Masz rację, że możesz je wdrożyć bezpośrednio, są one „odziedziczone” w Monad
(właściwie trochę bardziej skomplikowany).
Na przykład metoda, dla której zdecydowanie musisz zaimplementować interfejs Scalaz sequence
? Jest to metoda, która zamienia List
(lub bardziej ogólnie a Traversable
) kontekstów w jednym kontekście dla List
np .:
val randomlyGeneratedNumbers: List[Rng[Int]] = ...
randomlyGeneratedNumbers.sequence: Rng[List[Int]]
Ale tak naprawdę używa tylko Applicative[Rng]
(która jest nadklasą), a nie pełną mocą Monad
. Naprawdę nie mogę wymyślić niczego, co by używało Monad
bezpośrednio (istnieje kilka metod na MonadOps
, np. untilM
, ale nigdy nie użyłem żadnego z nich w gniewie), ale możesz chcieć Bind
w przypadku „opakowania”, w którym masz „wewnętrzny” Monad
„wewnątrz” twojego Rng
rzeczy, w którym to przypadku MonadTrans
jest użyteczny:
val a: Rng[Reader[Config, Int]] = ...
def f: Int => Rng[Reader[Config, Float]] = ...
//would be a pain to manually implement something to combine a and f
val b: ReaderT[Rng, Config, Int] = ...
val g: Int => ReaderT[Rng, Config, Float] = ...
b >>= g
Szczerze mówiąc, Applicative
jest prawdopodobnie wystarczający dla większości Monad
przypadki użycia, przynajmniej te najprostsze.
Oczywiście wszystkie te metody są dla ciebie mógłby zaimplementuj siebie, ale jak każda biblioteka, cały Scalaz polega na tym, że są one już zaimplementowane i mają standardowe nazwy, co ułatwia innym osobom zrozumienie twojego kodu.