Bu örnek nasıl modellenir
Bu, Reader monad ile nasıl modellenebilir?
Bu olmadığından emin değilim gerektiğini Reader ile modellenebilir, henüz tarafından şunlar olabilir:
- sınıfları, kodun Reader ile daha güzel oynamasını sağlayan işlevler olarak kodlamak
- Okuyucu ile işlevleri anlamak ve kullanmak için a'da oluşturma
Başlamadan hemen önce size bu cevap için faydalı olduğunu düşündüğüm küçük örnek kod ayarlamalarından bahsetmem gerekiyor. İlk değişiklik FindUsers.inactive
yöntemle ilgilidir. Geri dönmesine izin verdim, List[String]
böylece adres listesi UserReminder.emailInactive
yöntemde kullanılabilir . Yöntemlere basit uygulamalar da ekledim. Son olarak, örnek, Reader monad'ın aşağıdaki elle haddelenmiş bir sürümünü kullanacaktır:
case class Reader[Conf, T](read: Conf => T) { self =>
def map[U](convert: T => U): Reader[Conf, U] =
Reader(self.read andThen convert)
def flatMap[V](toReader: T => Reader[Conf, V]): Reader[Conf, V] =
Reader[Conf, V](conf => toReader(self.read(conf)).read(conf))
def local[BiggerConf](extractFrom: BiggerConf => Conf): Reader[BiggerConf, T] =
Reader[BiggerConf, T](extractFrom andThen self.read)
}
object Reader {
def pure[C, A](a: A): Reader[C, A] =
Reader(_ => a)
implicit def funToReader[Conf, A](read: Conf => A): Reader[Conf, A] =
Reader(read)
}
Modelleme adımı 1. Sınıfları işlev olarak kodlama
Belki bu isteğe bağlıdır, emin değilim, ama daha sonra anlamanın daha iyi görünmesini sağlar. Elde edilen işlevin curried olduğuna dikkat edin. Ayrıca, önceki yapıcı bağımsız değişkenlerini ilk parametreleri (parametre listesi) olarak alır. Bu şekilde
class Foo(dep: Dep) {
def bar(arg: Arg): Res = ???
}
olur
object Foo {
def bar: Dep => Arg => Res = ???
}
Her unutmayın Dep
, Arg
, Res
tipleri tamamen keyfi olabilir: bir demet, bir işlev veya basit bir türü.
İşte ilk ayarlamalardan sonra fonksiyonlara dönüştürülmüş örnek kod:
trait Datastore { def runQuery(query: String): List[String] }
trait EmailServer { def sendEmail(to: String, content: String): Unit }
object FindUsers {
def inactive: Datastore => () => List[String] =
dataStore => () => dataStore.runQuery("select inactive")
}
object UserReminder {
def emailInactive(inactive: () => List[String]): EmailServer => () => Unit =
emailServer => () => inactive().foreach(emailServer.sendEmail(_, "We miss you"))
}
object CustomerRelations {
def retainUsers(emailInactive: () => Unit): () => Unit =
() => {
println("emailing inactive users")
emailInactive()
}
}
Burada dikkat edilmesi gereken bir nokta, belirli işlevlerin tüm nesnelere değil, yalnızca doğrudan kullanılan parçalara bağlı olmasıdır. OOP sürüm UserReminder.emailInactive()
örneğinde userFinder.inactive()
burada çağrı yapacağı yerde, sadece inactive()
ilk parametrede kendisine iletilen bir işlevi çağırır .
Lütfen kodun soruda istenen üç özelliği gösterdiğini unutmayın:
- her bir işlevselliğin ne tür bağımlılıklara ihtiyaç duyduğu açıktır
- bir işlevin bağımlılıklarını diğerinden gizler
retainUsers
yöntemin Datastore bağımlılığı hakkında bilgi sahibi olması gerekmez
Modelleme adımı 2. Okuyucu'nun işlevleri oluşturmak ve çalıştırmak için kullanılması
Reader monad, yalnızca tümü aynı türe bağlı olan işlevleri oluşturmanıza izin verir. Bu genellikle bir durum değildir. Örneğimizde
FindUsers.inactive
bağlıdır Datastore
ve UserReminder.emailInactive
üzerinde EmailServer
. Bu sorunu çözmek için, tüm bağımlılıkları içeren yeni bir tür (genellikle Config olarak adlandırılır) tanıtılabilir, ardından işlevler değiştirilerek hepsi buna bağlı olacak ve ondan yalnızca ilgili veriler alınabilir. Bu, bağımlılık yönetimi açısından açıkça yanlıştır çünkü bu şekilde, bu işlevleri ilk etapta bilmemeleri gereken türlere de bağımlı hale getirirsiniz.
Neyse ki, işlevin Config
yalnızca bir kısmını parametre olarak kabul etse bile işlevin çalışmasını sağlamanın bir yolu olduğu ortaya çıktı . local
Reader'da tanımlanan bir yöntemdir . İlgili parçayı .NET Framework'ten çıkarmanın bir yolu sağlanmalıdır Config
.
Eldeki örneğe uygulanan bu bilgi şöyle görünecektir:
object Main extends App {
case class Config(dataStore: Datastore, emailServer: EmailServer)
val config = Config(
new Datastore { def runQuery(query: String) = List("john.doe@fizzbuzz.com") },
new EmailServer { def sendEmail(to: String, content: String) = println(s"sending [$content] to $to") }
)
import Reader._
val reader = for {
getAddresses <- FindUsers.inactive.local[Config](_.dataStore)
emailInactive <- UserReminder.emailInactive(getAddresses).local[Config](_.emailServer)
retainUsers <- pure(CustomerRelations.retainUsers(emailInactive))
} yield retainUsers
reader.read(config)()
}
Yapıcı parametrelerini kullanmanın avantajları
Böyle bir "iş uygulaması" için Reader Monad'ı hangi yönlerden kullanmak, yalnızca yapıcı parametrelerini kullanmaktan daha iyi olabilir?
Umarım bu cevabı hazırlayarak, basit kurucuları hangi yönlerden yeneceğini kendiniz yargılamayı kolaylaştırmışımdır. Yine de bunları sıralayacak olursam, işte listem. Sorumluluk Reddi: OOP geçmişim var ve Reader ve Kleisli'yi kullanmadığım için tam olarak takdir etmeyebilirim.
- Tekdüzelik - kavrama için ne kadar kısa / uzun olursa olsun, bu sadece bir Okuyucu ve onu başka bir örnekle kolayca oluşturabilirsiniz, belki de yalnızca bir tane daha Yapılandırma türü sunabilir ve
local
üzerine bazı çağrıları serpebilirsiniz . Bu nokta, IMO'dan ziyade bir zevk meselesidir, çünkü kurucuları kullandığınızda kimse, OOP'de kötü bir uygulama olarak kabul edilen yapıcıda çalışmak gibi aptalca bir şey yapmadıkça, sevdiğiniz şeyleri bestelemenizi engellemez.
- O ile ilgili tüm avantajlarını alır böylece Okuyucu, bir monad olan -
sequence
, traverse
yöntemler ücretsiz olarak uygulanmaktadır.
- Bazı durumlarda, Reader'ı yalnızca bir kez oluşturmayı ve onu çok çeşitli Yapılandırmalar için kullanmayı tercih edebilirsiniz. Yapıcılar ile kimse bunu yapmanızı engellemez, sadece gelen her Yapılandırma için tüm nesne grafiğini yeniden oluşturmanız gerekir. Bununla ilgili bir sorunum olmasa da (bunu her başvuru talebinde yapmayı tercih ediyorum), sadece hakkında spekülasyon yapabileceğim nedenlerden dolayı birçok kişi için açık bir fikir değil.
- Reader sizi, ağırlıklı olarak FP stilinde yazılmış uygulamalarla daha iyi oynayacak olan işlevleri daha fazla kullanmaya yönlendirir.
- Okuyucu endişeleri ayırır; bağımlılık sağlamadan oluşturabilir, her şeyle etkileşim kurabilir, mantığı tanımlayabilirsiniz. Aslında daha sonra ayrı ayrı tedarik edin. (Bu nokta için Ken Scrambler'a teşekkürler). Bu genellikle Reader'ın avantajı olarak duyulur, ancak bu düz kurucularla da mümkündür.
Reader'da neyi sevmediğimi de söylemek isterim.
- Pazarlama. Bazen, Reader'ın her tür bağımlılık için pazarlandığı izlenimine kapılıyorum, bu bir oturum çerezi mi yoksa bir veritabanı mı? Bana göre Reader'ı bu örnekteki e-posta sunucusu veya depo gibi pratik olarak sabit nesneler için kullanmanın pek bir anlamı yok. Bu tür bağımlılıklar için düz kurucular ve / veya kısmen uygulanan işlevleri çok daha iyi buluyorum. Esasen Reader size esneklik sağlar, böylece her aramada bağımlılıklarınızı belirleyebilirsiniz, ancak buna gerçekten ihtiyacınız yoksa, yalnızca vergisini ödersiniz.
- Örtülü ağırlık - Reader'ı dolaylı olarak kullanmak, örneğin okunmasını zorlaştırır. Öte yandan, gürültülü kısımları implicits kullanarak gizlediğinizde ve bazı hatalar yaptığınızda, derleyici bazen mesajları deşifre etmeniz zor olabilir.
- İle Töreni
pure
, local
ve kendi Yapılandırma sınıfları / bunun için dizilerini kullanarak oluşturma. Okuyucu sizi sorunlu etki alanıyla ilgili olmayan bazı kodlar eklemeye zorlar, bu nedenle kodda biraz gürültü ortaya çıkar. Öte yandan, kurucuları kullanan bir uygulama genellikle fabrika modelini kullanır, bu da sorunlu alanın dışından gelir, bu nedenle bu zayıflık o kadar da ciddi değildir.
Ya sınıflarımı işlevli nesnelere dönüştürmek istemiyorsam?
İstediğiniz. Teknik olarak bundan kaçınabilirsiniz, ancak FindUsers
sınıfı nesneye dönüştürmezsem ne olacağına bakın . İlgili anlama satırı şöyle görünecektir:
getAddresses <- ((ds: Datastore) => new FindUsers(ds).inactive _).local[Config](_.dataStore)
hangisi o kadar okunabilir değil, değil mi? Önemli olan şu ki, Reader'ın işlevler üzerinde çalışması, bu nedenle zaten bunlara sahip değilseniz, bunları satır içi olarak inşa etmeniz gerekir, bu genellikle o kadar da hoş değildir.