SORU
20 Mart 2015, Cuma


Okuyucu Bağımlılık Enjeksiyon için Monad: çoklu bağımlılıkları, iç içe geçmiş aramalar

Scala Bağımlılık Enjeksiyon hakkında sorulduğunda, yanıtlar çok Scalaz gelen ya da sadece kendi haddeleme ya da kullanarak Okuyucu Monad işaretleyin. Bir numara çok net makaleler açıklayan temel yaklaşım (örneğin Runar's talk, Jason's blog), ama yapmadım yönetmek için bulmak daha eksiksiz bir örnek, ve ben başarısız görmek avantajları bu yaklaşım içinde, örneğin, geleneksel bir "el" Dİ (the guide I wrote). Büyük ihtimalle bazı önemli nokta, soruyu bu yüzden kaçırıyorum.

Sadece örnek olarak, diyelim bu sınıflar var düşünün:

trait Datastore { def runQuery(query: String): List[String] }
trait EmailServer { def sendEmail(to: String, content: String): Unit }

class FindUsers(datastore: Datastore) {
  def inactive(): Unit = ()
}

class UserReminder(findUser: FindUsers, emailServer: EmailServer) {
  def emailInactive(): Unit = ()
}

class CustomerRelations(userReminder: UserReminder) {
  def retainUsers(): Unit = {}
}

Burada işler sınıfları ve çok güzel "geleneksel" Dİ yaklaşımlar, ancak bu tasarım iyi tarafı bir çift vardır: . ile oynayan kurucu parametreleri kullanarak modelleme yapıyorum

  • her işlevi açıkça numaralandırılmış bağımlılıkları vardır. Biz tür bağımlılıklar gerçekten işlevleri düzgün çalışması için gerekli olduğunu varsayalım
  • bağımlılıklar işlevleri, örneğin UserReminder FindUsers gerekiyor hiçbir fikri yok bir veri deposuna arasında gizli. İşlevleri bile ayrı birim derleme
  • biz kullanarak sadece saf Scala; uygulamaları olabilir kaldıraç değişmez sınıfları, üst düzey fonksiyonları, "iş mantığı" yöntemlerle dönüş değerleri sarılı IO monad eğer istediğimiz için yakalama etkileri vb.

Bu nasıl Okuyucu monad ile modellenmiş olabilir mi? Her türlü işlevsellik ihtiyaçlarını bağımlılıkları ne çok açıktır ki yukarıdaki özellikler korumak için iyi olurdu, ve başka bir işlev bağımlılıkları gizle. classes kullanarak bir uygulama ayrıntı daha; belki "" çözüm Okuyucu kullanarak monad başka bir şey. doğru not

Ya da akla getiriyor somewhat related question buldum:

  • tek bir ortam kullanarak tüm bağımlılıkları ile nesne
  • yerel ortamlar kullanarak < . "parfe" desen
  • yazın endeksli haritalar

Ancak, olması dışında (ama bu öznel) biraz fazla karmaşık gibi basit bir şey, tüm bu çözümler, örneğin retainUsers yöntem (hangi aramalar emailInactive, çağrı inactive bulmak için inaktif kullanıcı) bilmek Datastore bağımlılık yapabilmek için düzgün çağrı iç içe fonksiyonlar - yoksa yanılıyor muyum?

Böyle bir Monad Okuyucu" sadece yapıcı parametreleri kullanarak daha iyi bir "iş uygulama kullanarak,

CEVAP
17 HAZİRAN 2015, ÇARŞAMBA


Nasıl bu örnek modeli için

Bu nasıl Okuyucu monad ile modellenmiş olabilir mi?

Bu olup olmadığından emin değilimgerekirOkuyucu ile paraleldir, henüz.

  1. hangi fonksiyonları olarak sınıfları kodlama kod Okuyucu ile oynamak daha güzel yapar
  2. anlama için bir Okuyucu ile fonksiyonları oluşturma ve kullanma

Sadece başlangıç hemen önce bu cevap için yararlı hissettim küçük bir örnek kod ayarlamaları bahsetmek istiyorum. İlk değişim FindUsers.inactive yöntem hakkında. Bu adreslerin listesi kullanılabilir List[String] gitmelerine izin verdim. UserReminder.emailInactive yöntemi. Ayrıca yöntem basit uygulamaları ekledim. Son olarak, örnek bir kullanımı olacak elle sarılmış Okuyucu monad sürümü aşağıdaki:

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. İşlev olarak kodlama sınıfları

Belki isteğe bağlı bir şey, emin değilim, ama sonra kavrama için daha iyi görünmesini sağlar. Not, Bu elde edilen fonksiyon körili. Ayrıca ilk parametre (parametre listesi) olarak eski kurucu değişken alır. Bu şekilde

class Foo(dep: Dep) {
  def bar(arg: Arg): Res = ???
}
// usage: val result = new Foo(dependency).bar(arg)

olur

object Foo {
  def bar: Dep => Arg => Res = ???
}
// usage: val result = Foo.bar(dependency)(arg)

, *, Res *Arg22 türlerinin her biri tamamen keyfi olabilir unutmayın: bir demet, bir işlevi veya basit bir tip.

İşte ilk Ayarlamalar, fonksiyonlarına dönüştürülerek ö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 etmeniz gereken şey, belirli işlevleri doğrudan kullanılan parçalar üzerinde tüm nesneleri, ama sadece bağlı değil. UserReminder.emailInactive() örnek userFinder.inactive() burada diyebilir OOP sadece inactive()oturuyordu. - bir fonksiyonu ilk parametre olarak geçirilen.

Kodu soruda üç istenen özellikleri sergileyen lütfen dikkat:

  1. her türlü işlevsellik ihtiyaçlarını bağımlılıkları da artmaktadır
  2. gizler başka bir işlevi bağımlılıklar
  3. 29* *yöntem, Veri bağımlılığı hakkında bilmeniz gerekmez

Modelleme adım 2. Oluşturma işlevleri için Okuyucu kullanarak ve onları çalıştırın

Okuyucu monad yalnızca aynı tip bağımlı işlevler oluşturmak sağlar. Bu genellikle böyle değildir. Bizim örneğimizde FindUsers.inactive Datastore EmailServer UserReminder.emailInactive bağlıdır. Bu sorunu çözmek için bir bağımlılıkları içerir, değiştir o zaman yeni bir tür (genellikle Config olarak anılacaktır) tanıtmak olabilir onlar çok fonksiyonları bağlı ve ilgili veriler. Bu şekilde bu fonksiyonlar da bağımlı hale getirin, çünkü belli ki bağımlılık Yönetimi bakış açısı yanlış. haberi olmamaları gerektiğini türleri ilk etapta.

Neyse ki bu fonksiyon bir parametre olarak bir kısmı bunu kabul ederse bile Config ile iş yapmak için bir yol var, çıkıyor. Bir yöntem local, Okuyucu olarak tanımlanan deniyor. Config ilgili bölümünü almak için bir yol ile sağlanmış olması gerekiyor.

Bu bilgiyi eldeki örneğe uygulanan bu gibi 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ı parametreleri kullanarak avantaj

Böyle bir Monad Okuyucu" sadece yapıcı parametreleri kullanarak daha iyi bir "iş uygulama kullanarak,

Bu cevap hazırlayarak daha kolay hangi yönleri düz kurucular yeneceğine kendine hakim için yaptım umarım. Henüz bu numaralandır olsaydım, burada bu da benim listem. Yasal Uyarı: OOP bilgim var ve Okuyucu ve Kleisli takdir olabilirim bunları kullanmak istemiyorum tam olarak.

  1. Tekdüzelik - hiçbir mater ne anlama kısa/uzun, sadece Okuyucu ve kolayca başka bir tane oluşturabilirsiniz. örnek bir Config yazın tanıtılması ve local üzerinde görüşme yağmurlama belki de. Bu nokta, IMO kimse sana ne istersen oluşturmak için engeller kurucular kullanırken oldukça zevk meselesi, çünkü, tabii biri bir şey aptal gibi yapıyor OOP kötü bir uygulama olarak kabul edilir yapıcı iş.
  2. Okuyucu bir monad, tüm faydaları sequence, traverse yöntemleri ücretsiz uygulanması ile ilgili oluyor.
  3. Bazı durumlarda tercih Okuyucuya sadece bir kez kurmak ve Yapılandırmaları geniş bir yelpazede için kullanmak bulabilirsiniz. Kimse sana, bütün nesne grafiği her yeniden Yapılandırma için inşa etmek gerekir bunu yapmak için engeller kurucular ile gelen. (Ben bile uygulama için her istek üzerine yapmayı tercih ederim) bir sorun var, değil mi sadece spekülasyon olabilir nedenlerle bir çok insan için çok açık bir fikir.
  4. Okuyucu fonksiyonları daha iyi uygulama ağırlıklı olarak FP tarzında yazılmış olan oyun, daha fazla, kullanarak doğru iter.
  5. Okuyucu endişeleri ayırır; oluşturmak, her şeyi ile etkileşim, bağımlılıkları vermeden mantık tanımlayabilirsiniz. Aslında tedarik sonra, ayrı ayrı. (Bu işaret için teşekkürler Ken Karıştırıcı). Bu genellikle Okuyucu avantaj duydum, ama aynı zamanda düz kurucular ile mümkün.

Ayrıca Okuyucu olarak hoşuma gitmeyen şey söylemek istiyorum.

  1. Pazarlama. Bazen Okuyucu eğer bu ayrımı olmaksızın bağımlılıkları her türlü pazarlanıyor bu izlenimi alıyorum, bir oturum bir veritabanı veya çerez. Pratikte sabit nesneler için Okuyucu kullanarak küçük anlamı yok bana, e-posta gibi bu örnek, sunucu veya depo. Bu tür bağımlılıklar için kurucular ve/veya kısmen uygulanan fonksiyonları normal buluyorum daha iyi bir yolu. Aslında Okuyucu eğer her çağrı, ama bağımlılıkları belirtebilirsiniz esneklik sağlar gerçekten buna gerek yok, sen sadece kendi vergi ödemek.
  2. Örtülü ağırlık - implicits olmadan Okuyucusu kullanarak örnek okumak zor olur. Diğer taraftan, ne zaman gizle gürültülü parçaları implicits kullanarak ve bazı hata yapmak, derleyici bazen zor deşifre mesajlar verecektir.
  3. pure, local ve kendi Config sınıflar oluşturma / bunun için dizilerini kullanarak töreni. Okuyucu bazı kod eklemek için zorlar bu sorun, etki alanı hakkında, bu nedenle kodda biraz ses tanıtımı değil. Öte yandan, bir uygulama kurucular genellikle de sorun etki alanı dışında, Yani bu zayıflık bu değil mi, fabrika desen, kullanır kullanır ciddi.

Eğer işlevleri olan nesneler için derslerimi dönüştürmek istemiyorum eğer doğru değilse ne?

İstiyorum. Sen teknik olarakolabilirbunu önlemek için, ama sadece eğer FindUsers sınıf nesnesine dönüştürmek istemedim eğer doğru değilse ne olacağını bak. Anlama için ilgili çizgi gibi görünecektir:

getAddresses <- ((ds: Datastore) => new FindUsers(ds).inactive _).local[Config](_.dataStore)

bu okunabilir ki? Bu noktada Okuyucu fonksiyonları çalışır, onları zaten yok eğer doğru değilse, çoğu zaman bu güzel değil bunları satır, inşa etmek gerekir.

Bunu Paylaş:
  • Google+
  • E-Posta
Etiketler:

YORUMLAR

SPONSOR VİDEO

Rastgele Yazarlar

  • Jana Williams

    Jana William

    17 AĞUSTOS 2011
  • The Pet Collective

    The Pet Coll

    5 Ocak 2012
  • Chaîne de TheMoustic

    Chaîne de T

    5 Kasım 2006