Monadic fonksiyonlarınızda doğrulama ile hata monad'ı kullanmak veya doğrudan bağlantınızda doğrulama ile kendi monad'ınızı uygulamak daha mı iyi?


9

Kullanılabilirlik / sürdürülebilirlik için daha iyi bir tasarımın ne olduğunu ve topluluğa uymaktan daha iyi olanı merak ediyorum.

Veri modeli göz önüne alındığında:

type Name = String

data Amount = Out | Some | Enough | Plenty deriving (Show, Eq)
data Container = Container Name deriving (Show, Eq)
data Category = Category Name deriving (Show, Eq)
data Store = Store Name [Category] deriving (Show, Eq)
data Item = Item Name Container Category Amount Store deriving Show
instance Eq (Item) where
  (==) i1 i2 = (getItemName i1) == (getItemName i2)

data User = User Name [Container] [Category] [Store] [Item] deriving Show
instance Eq (User) where
  (==) u1 u2 = (getName u1) == (getName u2)

Örneğin öğeleri veya depoları ekleyerek Kullanıcıyı dönüştürmek için monadic işlevleri uygulayabilirim, ancak geçersiz bir kullanıcıyla sonuçlanabilirim, böylece bu monadic işlevlerin aldıkları ve oluşturdukları kullanıcıyı doğrulaması gerekir.

Yani, sadece:

  • bir hata monad'ına sarın ve monadik fonksiyonların doğrulamayı yapmasını sağlayın
  • bir hata monad'ına sarın ve tüketicinin uygun hata yanıtını atan sırayla monadik bir doğrulama işlevi bağlamasını sağlayın (böylece geçersiz bir kullanıcı nesnesini doğrulamamayı ve taşımamayı seçebilirler)
  • aslında kullanıcı üzerinde otomatik olarak her bağlama ile doğrulama yürüten kendi hata monad türünü oluşturma

3 yaklaşımın her birine olumlu ve olumsuz yanları görebiliyorum, ancak topluluk tarafından bu senaryo için daha yaygın olarak ne yapıldığını bilmek istiyorum.

Kod terimleriyle, seçenek 1 gibi bir şey:

addStore s (User n1 c1 c2 s1 i1) = validate $ User n1 c1 c2 (s:s1) i1
updateUsersTable $ someUser >>= addStore $ Store "yay" ["category that doesnt exist, invalid argh"]

seçenek 2:

addStore s (User n1 c1 c2 s1 i1) = Right $ User n1 c1 c2 (s:s1) i1
updateUsersTable $ Right someUser >>= addStore $ Store "yay" ["category that doesnt exist, invalid argh"] >>= validate
-- in this choice, the validation could be pushed off to last possible moment (like inside updateUsersTable before db gets updated)

seçenek 3:

data ValidUser u = ValidUser u | InvalidUser u
instance Monad ValidUser where
    (>>=) (ValidUser u) f = case return u of (ValidUser x) -> return f x; (InvalidUser y) -> return y
    (>>=) (InvalidUser u) f = InvalidUser u
    return u = validate u

addStore (Store s, User u, ValidUser vu) => s -> u -> vu
addStore s (User n1 c1 c2 s1 i1) = return $ User n1 c1 c2 (s:s1) i1
updateUsersTable $ someValidUser >>= addStore $ Store "yay" ["category that doesnt exist, invalid argh"]

Yanıtlar:


5

Yumruk kendime sorardım: Geçersiz Userbir kod hatası veya normalde meydana gelebilecek bir durum (örneğin, uygulamanıza yanlış bir girdi giriyor). Eğer bir hata ise, bunun asla gerçekleşmemesini sağlamaya çalışacağım ( akıllı yapıcılar kullanmak veya daha sofistike tipler oluşturmak gibi ).

Geçerli bir senaryo ise, çalışma zamanı sırasında bazı hata işlemeleri uygundur. Sonra sorardım: gerçekten bu benim için ne anlama gelir Userise geçersiz ?

  1. Geçersiz bir Userkodun başarısız olabileceği anlamına mı geliyor ? Kodunuzun bazı bölümleri a'nın Userher zaman geçerli olmasına güveniyor mu?
  2. Yoksa bunun daha sonra düzeltilmesi gereken bir tutarsızlık olduğu, ancak hesaplama sırasında hiçbir şeyi bozmadığı anlamına mı geliyor?

1. ise, kesinlikle bir tür hata monad (standart veya kendi) için gitmek istiyorum, aksi takdirde kodunuzun düzgün çalışacağını garanti kaybedersiniz.

Kendi monadınızı oluşturmak veya bir monad transformatör yığını kullanmak başka bir konudur, belki de bu yardımcı olacaktır: Hiç kimse vahşi bir Monad Transformatörü ile karşılaştı mı? .


Güncelleme: Genişletilmiş seçeneklerinize bakın:

  1. Gitmenin en iyi yolu gibi görünüyor. Belki de, gerçekten güvenli olmak için, yapıcıyı gizlemeyi Userve bunun yerine geçersiz bir örnek oluşturmaya izin vermeyen birkaç işlevi dışa aktarmayı tercih ederim . Bu şekilde, her gerçekleştiğinde düzgün bir şekilde işlendiğinden emin olacaksınız. Örneğin, bir oluşturmak için genel bir fonksiyonu Usergibi bir şey olabilir

    user :: ... -> Either YourErrorType User
    -- more generic:
    user :: (MonadError YourErrorType m) ... -> m User
    -- Or if you actually don't need to differentiate errors:
    user :: ... -> Maybe User
    -- or more generic:
    user :: (MonadPlus m) ... -> m User
    -- etc.
    

    Birçok kütüphane, örneğin benzer bir appropach almak Map, Setya da Seqonların değişmezleri itaat etmeyen bir yapı oluşturmak mümkün değildir, böylece altta yatan uygulanmasını gizlemek.

  2. Doğrulamayı sonuna kadar ertelerseniz ve Right ...her yerde kullanırsanız , artık bir monad'a ihtiyacınız yoktur. Sonunda sadece saf hesaplamalar yapabilir ve olası hataları çözebilirsiniz. IMHO bu yaklaşım çok risklidir, çünkü geçersiz bir kullanıcı değeri başka yerlerde geçersiz verilere neden olabilir, çünkü hesaplamayı yakında durdurmadınız. Ve, başka bir yöntemin kullanıcıyı tekrar geçerli olacak şekilde güncellemesi durumunda, bir yerde geçersiz veriye sahip olursunuz ve bunu bile bilmezsiniz.

  3. Burada birkaç sorun var.

    • En önemlisi, bir monad'ın sadece değil, herhangi bir tip parametresini kabul etmesi gerektiğidir User. Yani herhangi bir kısıtlama olmadan validatetip olması gerekir . Bu nedenle , girişlerini doğrulayan böyle bir monad yazmak mümkün değildir , çünkü tamamen polimorfik olmalıdır.u -> ValidUser uureturnreturn
    • Daha sonra, anlamadığım şey case return u of, tanımında eşleşmenizdir >>=. Ana nokta, ValidUsergeçerli ve geçersiz değerleri ayırt etmek olmalı ve böylece monad bunun her zaman doğru olduğundan emin olmalıdır. Yani basitçe olabilir

      (>>=) (ValidUser u) f = f u
      (>>=) (InvalidUser u) f = InvalidUser u
      

    Ve bu zaten çok benziyor Either.

Genellikle, özel bir monad kullanırım

  • İhtiyacınız olan işlevselliği sağlayan mevcut monad yoktur. Mevcut monadların genellikle birçok destek işlevi vardır ve daha da önemlisi, monad transformatörleri vardır, böylece onları monad yığınlarına dönüştürebilirsiniz.
  • Veya bir monad yığını olarak tanımlanamayacak kadar karmaşık bir monad'a ihtiyacınız varsa.

Son iki noktanız çok değerli ve onları düşünmedim! Kesinlikle aradığım bilgelik, düşüncelerinizi paylaştığınız için teşekkürler, kesinlikle # 1 ile gideceğim!
Jimmy Hoffa

Sadece bütün gece bütün modülü bađladýn ve sen ölüsün. Doğrulama yöntemimi, tüm model güncellemelerini yaptığım birkaç anahtar birleştiriciye atladım ve aslında bu çok daha mantıklı. Gerçekten # 3'ten sonra gidecektim ve şimdi bu yaklaşımın nasıl esnek olmadığını görüyordum, bu yüzden beni düzleştirdiğin için bir ton teşekkürler!
Jimmy Hoffa
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.