2015-04-06 9 views
8

Al sınıfı Functor:Tip sınıflarında tip kurucuların herhangi bir avantajı var mı? örneğin

class Functor a 
instance Functor Maybe 
İşte

Maybe bir tür yapıcı olduğunu.

Ama biz başka iki yolla yapabilirsiniz:

Öncelikle, çok parametreli tip sınıfları kullanarak:

class MultiFunctor a e 
instance MultiFunctor (Maybe a) a 

İkincisi kullanarak tipi aileleri:

class MonoFunctor a 
instance MonoFunctor (Maybe a) 

type family Element 
type instance Element (Maybe a) a 

Şimdi bariz bir tane var Son iki yöntemin avantajı, yani şu gibi şeyler yapmamıza izin verir:

instance Text Char 

Veya:

instance Text 
type instance Element Text Char 

yüzden monomorfik konteynerlerin çalışabilirsiniz.

İkinci avantaj, son parametresi olan type parametresine sahip olmayan tiplerin örneklerini yapabilmemizdir. biz Either stil türünü yapmak ama etrafında tipleri yanlış şekilde koymak Diyelim:

data Silly t errorT = Silly t errorT 

instance Functor Silly -- oh no we can't do this without a newtype wrapper 

instance MultiFunctor (Silly t errorT) t 

Oysa gayet iyi çalışıyor ve

instance MonoFunctor (Silly t errorT) 
type instance Element (Silly t errorT) t 

da iyidir.

Bu tip esneklik tanımlarında yalnızca tam türlerin (tip imzaları değil) kullanılmasıyla ilgili bu esneklik avantajları göz önüne alındığında, GHC kullandığınızı ve uzantıları kullanmayı düşünmediğiniz varsayılarak orijinal stil tanımını kullanmanın bir nedeni var mıdır? Yani, bir tip kurucuyu, çok tipli tip sınıflarla veya tip ailelerle yapamayacağınız bir tip sınıfında tam bir tip değil, özel bir şey yapabiliyor musunuz?

cevap

4

Bir şey için geleneksel functor sınıfı çok daha basit. Bu tek başına Haskell and not Python olsa bile, tercih etmek için geçerli bir sebeptir. Ve aynı zamanda, bir fenerin ne olması gerektiğinin daha iyi bir matematiksel fikrini de temsil eder: nesnelerden nesnelere (f :: *->*), ekstra özellikli (->Constraint) her bir (forall (a::*) (b::*)) morfizmin (a->b) karşılık gelen bir morfizme kaldırılması nesne eşleştirildi (-> f a->f b).
Bunun hiçbiri sınıfın * -> * -> Constraint sürümünde veya TypeFamilies eşdeğerinde çok net olarak görülemez.

Daha pratik bir hesapta, evet, yalnızca (*->*)->Constraint sürümünde yapabileceğiniz şeyler vardır. Özellikle

, ya bu kısıt hemen size garanti tüm Haskell türleri MultiFunctor için olası her içerdiği türünü birer birer kontrol etmeniz gerekir, oysa funktor içine koyabilirsiniz geçerli nesneleri olmasıdır.Bazen bu sadece mümkün değildir (veya bu?), Sen sonsuz sayıda türleri üzerinde haritalama yaparken olduğu gibi: Bu a türüne değil, aynı zamanda keyfi üzerinde sadece f ait Applicative örneğini kullandığı

data Tough f a = Doable (f a) 
       | Tough (f (Tough f (a, a))) 

instance (Applicative f) = Semigroup (Tough f a) where 
    Doable x <> Doable y = Tough . Doable $ (,)<$>x<*>y 
    Tough xs <> Tough ys = Tough $ xs <> ys 
    -- The following actually violates the semigroup associativity law. Hardly matters here I suppose... 
    xs <> Doable y = xs <> Tough (Doable $ fmap twice y) 
    Doable x <> ys = Tough (Doable $ fmap twice x) <> ys 

twice x = (x,x) 

Not bunların tuplleri. Bunu bir MultiParamTypeClasses - veya TypeFamilies temelli uygulama sınıfıyla nasıl ifade edeceğinizi göremiyorum. (Siz ancak olmadan, uygun bir GADT Tough yaparsanız Muhtemelen değil ... mümkün olabilir.)

BTW, bu örnek, temelde uzunluğunun salt okunur vektörleri ifade – görünebilir belki de işe yaramaz değildir Monatik bir durumda 2 .

+0

Geleneksel yaklaşımla, özellikle de basit bir örnekle, tip ailelerle elde edilemeyen objektif hakkında daha fazla bilgi verir misiniz? – Clinton

+0

@ Clinton: Bunu düşündüm; 'lens' _could_ muhtemelen bir '* -> * -> Constraint 'functor sınıfı ile ifade edilebilir (oldukça garip de olsa). Farklı bir örnek ekledim. – leftaroundabout

2

Genişletilmiş varyant gerçekten daha esnektir. Ör. tarafından Oleg Kiselyov tanımlamak için restricted monads. Kabaca, sen monad örnekleri a ve b üzerinde Parametrelendirme sağlayan

class MN2 m a where 
    ret2 :: a -> m a 

class (MN2 m a, MN2 m b) => MN3 m a b where 
    bind2 :: m a -> (a -> m b) -> m b 

sahip olabilir.

import Data.Set as Set 

instance MN2 Set.Set a where 
    -- does not require Ord 
    return = Set.singleton 

instance Prelude.Ord b => MN3 SMPlus a b where 
    -- Set.union requires Ord 
    m >>= f = Set.fold (Set.union . f) Set.empty m 

Not o Ord kısıtlama nedeniyle, sınırsız monads kullanılarak Monad Set.Set tanımlamak mümkün değildir çünkü daha: Eğer başka sınıf üyeleriyle o tür kısıtlayabilir çünkü bu yararlıdır. Gerçekten de, monad sınıfı monadın her türden kullanılabilir olmasını gerektirir.

Ayrıca bkz .: parameterized (indexed) monad.

+2

Şimdilik bir süredir kısıtlı monadlar vb. 'ConstraintKinds' kullanarak daha basit bir şekilde yapılabilir. Eğer [bunun uygulanmasını ilan edebilirim] (http://hackage.haskell.org/package/constrained-categories) ... – leftaroundabout

12

Teklifleriniz, var olan Functor tanımına ilişkin bazı önemli ayrıntıları göz ardı edin çünkü sınıfın üyesi işlevinde ne olacağını yazarken ayrıntılarını çalışmadınız.

class MultiFunctor a e where 
    mfmap :: (e -> ??) -> a -> ???? 

instance MultiFunctor (Maybe a) a where 
    mfmap = ??????? 

anda fmap önemli bir özelliği, ilk bağımsız değişken türleri değiştirebilir olmasıdır. fmap show :: (Functor f, Show a) => f a -> f String. Bunu atlatamazsın ya da fmap'un değerini kaybedersin. Yani gerçekten, MultiFunctor bu çıkarım mümkün yakın en az yapmak denemek için olmuştur

class MultiFunctor s t a b | s -> a, t -> b, s b -> t, t a -> s where 
    mfmap :: (a -> b) -> s -> t 

instance (a ~ c, b ~ d) => MultiFunctor (Maybe a) (Maybe b) c d where 
    mfmap _ Nothing = Nothing 
    mfmap f (Just a) = Just (f a) 

Not sadece nasıl inanılmaz derecede karmaşık ... gibi daha bakmak gerekir. Tüm işlevsel bağımlılıklar, tüm yerlerin üzerinde ek açıklama türleri olmadan örnek seçimine izin verecek şekilde yerleştirilmiştir. (Orada birkaç olası işlevsel bağımlılıkları kaçırmış olabilirim!) Örnek, örnek seçiminin daha güvenilir olmasını sağlamak için bazı çılgın tip eşitlik kısıtlamaları geliştirdi. Ve en kötü tarafı - bu hala fmap muhakeme için daha kötü özelliklere sahiptir. Bu tabii ki, kırık

instance MultiFunctor (Maybe Int) (Maybe Int) Int Int where 
    mfmap _ Nothing = Nothing 
    mfmap f (Just a) = Just (if f a == a then a else f a * 2) 

- ama önce bile mümkün değildi yeni bir şekilde kırdığını: olmasaydı benim önce geçtiği farz

, böyle bir örneğini yazabiliriz. Functor tanımının önemli bir parçası,ve bfmap türlerinin örnek tanımında hiçbir yerde görünmemesidir.Sadece sınıfa bakarak, programcıya fmap'un davranışının a ve b türlerine bağlı olmadığını söylemesi yeterlidir. Bu garantiyi bedavaya kavuştun. Bu örneklerin doğru yazıldığından emin olmanıza gerek yok.

fmap bu garantiyi size verdiğinden, bir örnek tanımlarken hem Functor kanunlarını da denetlemeniz gerekmez. fmap id x == x yasasını kontrol etmek yeterlidir. Birinci yasa ispatlandığında ikinci yasa ücretsiz olarak gelir. Ancak, sağladığım mfmap ile, ikinci yasa olmasa bile mfmap id x == x doğrudur.

mfmap uygulayıcısı olarak, uygulamanızın doğru olduğunu kanıtlamak için daha fazla işiniz var. Bir kullanıcı olarak, sistem, sistemin doğruluğunu garanti edemeyeceğinden, uygulamanın doğruluğuna daha fazla güvenmeniz gerekir.

Diğer sistemler için daha eksiksiz örneklerle çalışıyorsanız, fmap'un tüm işlevlerini desteklemek istiyorsanız, bunların çok sayıda sorunu olduğunu görürsünüz. Ve bu yüzden gerçekten kullanılmıyorlar. Yardımcı programda yalnızca küçük bir kazanç için lot karmaşıklık eklerler.

+0

Vay, bu sınıfın ne kadar çabuk tükeneceğini fark etmemiştim bile '* -> * 'türüyle uygulamıyorum (sadece mfmap :: MultiFunctor bd => (a-> b) -> e-> d'' nin hile yapması gerektiğini düşündüm, ama hiçbir şekilde). – leftaroundabout