Haskell: Typeclass ve bir fonksiyonun geçmesi


16

Bana göre, bir typeclass kullanmak yerine her zaman işlev argümanlarını iletebilirsiniz. Örneğin, eşitlik tip sınıfını tanımlamak yerine:

class Eq a where 
  (==)                  :: a -> a -> Bool

Ve tür argümanını belirtmek için diğer işlevlerde kullanmak aşağıdakilerin bir örneği olmalıdır Eq:

elem                    :: (Eq a) => a -> [a] -> Bool

elemİşlevimizi sadece bir typeclass kullanmadan tanımlayamaz ve bunun yerine işi yapan bir işlev argümanı iletemez miyiz?


2
buna sözlük geçişi denir. Typeclass kısıtlamalarını örtük argümanlar olarak düşünebilirsiniz.
Poscat

2
Bunu yapabilirsin, ama belli ki bir işlevi geçmek zorunda değilsiniz ve sadece türüne bağlı olarak "standart" bir tane kullanmak zorunda.
Robin Zigmond

2
Böyle koyabilirsiniz, evet. Ama en azından bir başka önemli avantajı daha olduğunu iddia ediyorum: belirli bir "arayüz" veya özellikler kümesi uygulayan her tür üzerinde çalışan polimorfik fonksiyonlar yazma yeteneği. Bence typeclass kısıtlamaları, açıkça ekstra işlev argümanları geçmez bir şekilde ifade eder. Özellikle, birçok yazının yerine getirmesi gereken (ne yazık ki örtük) "yasalar" nedeniyle. Bir Monad mkısıtlama bana tür a -> m ave ek işlev argümanlarını iletmekten daha fazlasını söylüyor m a -> (a -> m b) -> m b.
Robin Zigmond

1
Ayrıca bakınız typeclasses esastır?
luqui

1
TypeApplicationsUzantısı örtük fikir açık yapmanıza olanak sağlar. ve özellikle değerler olarak (==) @Int 3 5karşılaştırır . -Spesifik karşılaştırma işlevinin kendisi yerine türe özgü eşitlik işlevleri sözlüğünde bir anahtar olarak düşünebilirsiniz . 35Int@IntInt
chepner

Yanıtlar:


19

Evet. Buna "sözlük aktarma stili" denir. Bazen bazı zor şeyler yaptığımda, bir daktilo yazıp bir sözlüğe dönüştürmem gerekiyor, çünkü sözlük geçişi daha güçlü 1 , ancak genellikle oldukça hantal, kavramsal olarak basit kod oldukça karmaşık görünüyor. Tipik kelimeleri taklit etmek için bazen Haskell olmayan dillerde sözlük aktarma stilini kullanıyorum (ancak bunun genellikle göründüğü kadar büyük bir fikir olmadığını öğrendim).

Tabii ki, ifade gücünde bir fark olduğunda, bir değiş tokuş vardır. DPS kullanılarak yazılmışsa, belirli bir API'yı daha fazla şekilde kullanabilirsiniz, ancak yapamıyorsanız API daha fazla bilgi alır. Bunun pratikte Data.Setortaya çıkmasının bir yolu , Ordtip başına sadece bir sözlük olması gerçeğine dayanmaktadır . SetSaklar unsurları göre sıralanır Ordtek sözlükle kümesi oluşturmak ve ardından farklı birini kullanarak bir öğesi yerleştirdim, DPS ile mümkün olacağı gibi, sen kırılabilir ve eğer Set'nin değişmez ve kilitlenmesine neden. Bu teklik sorunu, hayali bir varoluş kullanılarak azaltılabilirsözlüğü işaretlemek için yazın, ancak yine de API'daki biraz can sıkıcı karmaşıklığın pahasına. Bu aynı zamanda TypeableAPI'da hemen hemen aynı şekilde görünür.

Teklik biti çok sık ortaya çıkmaz. Hangi tipler büyük sizin için kod yazmaktır. Örneğin,

catProcs :: (i -> Maybe String) -> (i -> Maybe String) -> (i -> Maybe String)
catProcs f g = f <> g

girdi alan ve çıktı veren iki "işlemci" alır ve bunları birleştirerek düzleştirerek NothingDPS'de şöyle bir şey yazılması gerekir:

catProcs f g = (<>) (funcSemi (maybeSemi listSemi)) f g

Zaten tür imzasında zaten yazmış olsak da, onu tekrar kullandığımız türü hecelemek zorunda kaldık ve bu bile gereksizdi çünkü derleyici zaten tüm türleri biliyor. Belirli Semigroupbir türde bir yapı oluşturmanın tek bir yolu olduğundan , derleyici bunu sizin için yapabilir. Bu, bir çok parametrik örneği tanımlamaya ve türlerinizin yapısını sizin için hesaplamak için, birleştiricilerde olduğu gibi kullanmaya başladığınızda "bileşik faiz" tipi bir etkiye sahiptir Data.Functor.*ve bu, deriving viatemelde tüm Sizin için yazılmış tipinizin "standart" cebirsel yapısı.

Ve beni, bilgileri daktilo kontrolüne ve çıkarsamaya besleyen MPTC'lere ve fundeps'e bile başlama. Hiç böyle bir şeyi DPS'ye dönüştürmeyi denemedim - bunun birçok tür eşitlik kanıtı geçirmeyi gerektirdiğinden şüpheleniyorum - ama her durumda beynim için rahat olacağımdan çok daha fazla iş olacağından eminim ile.

-

1 U nless kullandığınız reflectioniktidarda eşdeğer hale bu durumda - fakat reflectionaynı zamanda kullanım külfetli olabilir.



DPS ile ifade edilen fundeps ile çok ilgileniyorum. Bu konuda bazı tavsiye edilebilir kaynaklar biliyor musunuz? Her neyse, çok anlaşılır bir açıklama.
bob

@bob, hazırlıksız değil, ama ilginç bir keşif olurdu. Belki bu konuda yeni bir soru sorabilirsiniz?
luqui

5

Evet. Bu (sözlük geçirme olarak adlandırılır) temel olarak derleyicinin yine de örnekleme yapmak için yaptığı şeydir. Kelimenin tam anlamıyla yapılan bu işlev için biraz şöyle görünecektir:

elemBy :: (a -> a -> Bool) -> a -> [a] -> Bool
elemBy _ _ [] = False
elemBy eq x (y:ys) = eq x y || elemBy eq x ys

Sesli arama elemBy (==) x xsartık eşdeğerdir elem x xs. Ve bu özel durumda, bir adım daha ileri gidebilirsiniz: eqher seferinde aynı ilk argümana sahiptir, böylece bunu uygulamak için arayanın sorumluluğunu yapabilir ve bununla sonuçlayabilirsiniz:

elemBy2 :: (a -> Bool) -> [a] -> Bool
elemBy2 _ [] = False
elemBy2 eqx (y:ys) = eqx y || elemBy2 eqx ys

Sesli arama elemBy2 (x ==) xsartık eşdeğerdir elem x xs.

...Bekle. Bu sadece any. (Ve aslında, standart kütüphanedeelem = any . (==) .)


AFAIU sözlük geçişi, Scala'nın tip sınıflarını kodlamaya yaklaşımıdır. Bu ekstra argümanlar olarak ilan edilebilir implicitve derleyici bunları sizin için kapsamdan enjekte eder.
michid
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.