Neden işlevler için bir sınıf sınıfı yok?


17

Etrafta dolaştığım bir öğrenme probleminde, uygulama, besteleme vb. İşlemleri için bir tip sınıfına ihtiyacım olduğunu fark ettim.

  1. Bir işlevin temsilini, sanki işlevin kendisiymiş gibi ele almak uygun olabilir, böylece işlevin dolaylı olarak uygulanması bir yorumlayıcı kullanır ve işlevler oluşturmak yeni bir açıklama oluşturur.

  2. Fonksiyonlar için bir tip sınıfınız olduğunda, özel fonksiyon türleri için türetilmiş sınıflar olabilir - benim durumumda, ters çevrilebilir fonksiyonlar istiyorum.

Örneğin, tamsayı ofsetleri uygulayan fonksiyonlar bir tamsayı içeren bir ADT ile temsil edilebilir. Bu işlevleri uygulamak, tamsayı eklemek anlamına gelir. Kompozisyon, sarılmış tamsayılar eklenerek uygulanır. Ters fonksiyonun tamsayısı reddedilir. Kimlik işlevi sıfırı sarar. Sabit işlev sağlanamaz çünkü onun için uygun bir temsil yoktur.

Elbette, değerleri gerçek Haskell işlevleriymiş gibi hecelemeye gerek yok, ama bir kez fikre sahip olduğumda, böyle bir kütüphanenin zaten var olması ve hatta standart yazımları kullanması gerektiğini düşündüm. Ama Haskell kütüphanesinde böyle bir sınıf bulamıyorum.

Data.Function modülünü buldum , ancak tip sınıfı yok - sadece Prelude'da bulunan bazı ortak işlevler.

Öyleyse - neden fonksiyonlar için bir sınıf sınıfı yok? "Sadece olmadığı için" veya "düşündüğünüz kadar yararlı olmadığı için" mi? Ya da belki fikirle ilgili temel bir sorun var mı?

Şimdiye kadar düşündüğüm en büyük sorun, gerçek işlevler üzerindeki işlev uygulamasının, bir döngü sorununu önlemek için derleyici tarafından özel olarak kastedilmesi gerektiğidir - bu işlevi uygulamak için işlev uygulama işlevini uygulamam gerekir, ve bunu yapmak için işlev uygulama işlevini çağırmak ve bunu yapmak gerekir ...

Daha Fazla İpucu

Neyi hedeflediğimi göstermek için örnek kod ...

{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-}

--  In my first version, Doable only had the one argument f. This version
--  seemed to be needed to support the UndoableOffset type.
--
--  It seems to work, but it also seems strange. In particular,
--  the composition function - a and b are in the class, but c isn't,
--  yet there's nothing special about c compared with a and b.
class Doable f a b where
  fwdApply :: f a b -> a -> b
  compDoable :: f b c -> f a b -> f a c

--  In the first version, I only needed a constraint for
--  Doable f a b, but either version makes sense.
class (Doable f a b, Doable f b a) => Undoable f a b where
  bwd      :: f a b -> f b a

  bwdApply :: f a b -> b -> a
  bwdApply f b = fwdApply (bwd f) b

--  Original ADT - just making sure I could wrap a pair of functions
--  and there were no really daft mistakes.
data UndoableFn a b = UFN { getFwd :: a -> b, getBwd :: b -> a }

instance Doable UndoableFn a b where
  fwdApply = getFwd
  compDoable f g = UFN ((getFwd f) . (getFwd g)) ((getBwd g) . (getBwd f))

instance Undoable UndoableFn a b where
  bwd f    = UFN (getBwd f) (getFwd f)
  bwdApply = getBwd

--  Making this one work led to all the extensions. This representation
--  can only represent certain functions. I seem to need the typeclass
--  arguments, but also to need to restrict which cases can happen, hence
--  the GADT. A GADT with only one constructor still seems odd. Perhaps
--  surprisingly, this type isn't just a toy (except that the whole thing's
--  a toy really) - it's one real case I need for the exercise. Still a
--  simple special case though.
data UndoableOffset a b where
  UOFF :: Int -> UndoableOffset Int Int

instance Doable UndoableOffset Int Int where
  fwdApply (UOFF x) y = y+x
  compDoable (UOFF x) (UOFF y) = UOFF (x+y)

instance Undoable UndoableOffset Int Int where
  bwdApply (UOFF x) y = y-x
  bwd (UOFF x) = UOFF (-x)

--  Some value-constructing functions
--  (-x) isn't shorthand for subtraction - whoops.
undoableAdd :: Int -> UndoableFn Int Int
undoableAdd x = UFN (+x) (\y -> y-x)

undoableMul :: Int -> UndoableFn Int Int
undoableMul x = UFN (*x) (`div` x)

--  With UndoableFn, it's possible to define an invertible function
--  that isn't invertible - to break the laws. To prevent that, need
--  the UFN constructor to be private (and all public ops to preserve
--  the laws). undoableMul is already not always invertible.
validate :: Undoable f a b => Eq a => f a b -> a -> Bool
validate f x = (bwdApply f (fwdApply f x)) == x

--  Validating a multiply-by-zero invertible function shows the flaw
--  in the validate-function plan. Must try harder.
main = do putStrLn . show $ validate (undoableAdd 3) 5
          putStrLn . show $ validate (undoableMul 3) 5
          --putStrLn . show $ validate (undoableMul 0) 5
          fb1 <- return $ UOFF 5
          fb2 <- return $ UOFF 7
          fb3 <- return $ compDoable fb1 fb2
          putStrLn $ "fwdApply fb1  3 = " ++ (show $ fwdApply fb1  3)
          putStrLn $ "bwdApply fb1  8 = " ++ (show $ bwdApply fb1  8)
          putStrLn $ "fwdApply fb3  2 = " ++ (show $ fwdApply fb3  2)
          putStrLn $ "bwdApply fb3 14 = " ++ (show $ bwdApply fb3 14)

Uygulama, birleştirilmiş değerlerin eşit olmadığı, ancak bu ters çevrilebilir işlevler - Prolog tarzı mantıkla değil, a = f(b)kısıtlamalarla ilişkili olduğu bir tür birleştirmeyi içerir a = b. Kompozisyonun çoğu sendika bulma yapısının optimize edilmesinden kaynaklanacaktır. Tersine çevirme ihtiyacı açık olmalıdır.

Birleştirilmiş bir kümedeki hiçbir öğenin kesin bir değeri yoksa, belirli bir öğe yalnızca o birleştirilmiş kümedeki başka bir öğeye göre ölçülebilir. Bu yüzden "gerçek" işlevleri kullanmak istemiyorum - bu göreceli değerleri hesaplamak. Tüm fonksiyon yönünü düşürebilirim ve sadece mutlak ve göreceli miktarlara sahip olabilirim - muhtemelen sadece sayılara / vektörlere ihtiyacım var ve (+)- ama iç mimarım astronot eğlencesini istiyor.

Bağlantıları tekrar parçalara ayırmanın tek yolu geri izlemedir ve her şey saftır - sendika bulma anahtarları kullanarak bir IntMap"işaretçiler" olarak yapılacaktır. Basit sendika bulma çalışması var, ancak henüz ters çevrilebilir işlevleri eklemediğim için, burada listelemenin bir anlamı yok.

Uygulayıcı, Monad, Ok vb.

Sağlamak için fonksiyon soyutlama sınıfına ihtiyacım olan ana işlemler uygulama ve kompozisyon. Kulağa tanıdık geliyor - örneğin Applicative (<*>), Monad (>>=)ve Arrow (>>>)hepsi kompozisyon işlevleridir. Ancak benim durumumda işlev soyutlamasını uygulayan türler, bir işlevi temsil eden, ancak bir işlevi olmayan (ve içeremeyen) ve yalnızca bazı sınırlı işlev kümesini temsil edebilen bazı veri yapısı içerecektir.

Kodun açıklamasında belirtildiği gibi, bazen "birleştirilmiş" kümedeki hiçbir öğenin kesin bir değeri olmadığından, yalnızca bir öğeyi diğerine göre ölçebilirim. Genel olarak, sağlanan bazı fonksiyonların (birlik / ağaçta ortak bir ataya kadar yürürken) ve birkaç ters fonksiyonun (diğerine geri yürüme) bileşimi olacak bu fonksiyonun bir temsilini türetmek istiyorum. öğesi).

Basit durum - orijinal "fonksiyonlar" tamsayı-ofset "fonksiyonlar" ile sınırlı olduğunda, ben oluşturulan sonucu bir tamsayı-ofset "fonksiyonu" olarak istiyorum - bileşen ofsetleri ekleyin. Bu, kompozisyon işlevinin neden uygulama işlevinin yanı sıra sınıfta olması gerektiğinin büyük bir parçasıdır.

Bu araçlar Ben işlemlerini sağlayamaz pure, returnya arrbenim türleri için, ben kullanamaması için Applicative, Monadya da Arrow.

Bu, bu tür bir başarısızlık değil - soyutlamaların uyumsuzluğu. İstediğim soyutlama basit saf bir işleve sahip. Örneğin herhangi bir yan etki yoktur ve tüm işlevler için geçerli olan standardın (.) Eşdeğeri dışında işlevlerin sıralanması ve oluşturulması için uygun bir gösterim oluşturmaya gerek yoktur.

Ben örnek verebilirimCategory . Tüm işlevsel şeylerimin bir kimlik sağlayabileceğinden eminim, ancak muhtemelen buna ihtiyacım yok. Ancak Categoryuygulamayı desteklemediğinden, yine de bu işlemi eklemek için türetilmiş bir sınıfa ihtiyacım var.


2
Bana çılgın deyin, ama bunu açıklamak ve yazmak için tanımladığınız gibi bir tip dersi düşündüğümde, pratik fonksiyonlar olduğunu düşünüyorum. Belki de düşündüğünüz tip sınıfı budur?
Jimmy Hoffa

1
ApplicativeOldukça doğru olduğunu düşünmüyorum - değerlerin işlevlerin yanı sıra sarılmasını gerektirir, ancak yalnızca işlevleri sarmak istiyorum ve sarılmış işlevler gerçekten işlevler, oysa sarılmış işlevlerim normalde (içinde en genel durum, işlevleri tanımlayan AST'lerdir). Nerede <*>türü vardır f (a -> b) -> f a -> f b, ben türüyle bir uygulama operatörü istiyorum ve etki alanını belirlemek ve sarılı fonksiyonun değer kümesi, ama ne mahfaza içine değil (mutlaka) gerçek işlevdir. Oklarda - muhtemelen, bir göz atacağım. g a b -> a -> bab
Steve314

3
ters istiyorsanız bu bir grup anlamına gelmez mi?
jk.

1
@jk. büyük bir nokta, fonksiyonların tersleri hakkında okunacak çok şey olduğunu fark ederek OP'nin aradığını bulmasına yol açabilir. İşte konu hakkında ilginç okumalar. Ancak tersi bir google for google işlevi bol miktarda meraklı okuma içeriği sağlar. Belki de sadece Data.Group istiyor
Jimmy Hoffa

2
@ Steve314 Kompozisyonlu fonksiyonların monoidal bir kategori olduğunu düşündüm. Alan adı ve alan adı her zaman aynı ise bunlar bir monoiddir.
Tim Seguine

Yanıtlar:


14

Kendilerini "işlev-y" şeylerini temsil eden pazarlayan herhangi bir fikir bilmiyorum. Ama yaklaşan birkaç tane var

Kategoriler

Kimlikleri ve bileşimi olan basit bir işlev kavramınız varsa, bir kategoriniz var demektir.

class Category c where
  id :: c a a
  (.) :: c b c -> c a b -> c a c

Dezavantajı nesne kümeleri ile güzel kategori örneği oluşturamazsınız olmasıdır ( a, bve c). Sanırım özel bir kategori sınıfı oluşturabilirsiniz.

Oklar

İşlevleriniz bir ürün kavramına sahipse ve rastgele işlevler enjekte edebilirse, oklar sizin için

 class Arrow a where
   arr :: (b -> c) -> a b c
   first :: a b c -> a (b, d) (c, d)
   second :: a b c -> a (d, b) (d, c)

ArrowApply İstediğiniz şey için önemli görünen bir uygulama kavramına sahiptir.

Applicatives

Başvurular sizin uygulama fikrinize sahiptir, bunları bir AST'de işlev uygulamasını temsil etmek için kullandım.

class Functor f => Applicative f where
  pure :: a -> f a
  (<*>) :: f (a -> b) -> f b -> f c

Başka birçok fikir var. Ancak ortak bir tema, işlevinizi temsil eden bir veri yapısı oluşturmak ve onu bir yorumlama işlevine iletmektir.

Bu aynı zamanda kaç serbest monadın çalıştığıdır. Cesur hissediyorsanız, bunlara alay etmenizi öneririm, önerdiğiniz şeyler için güçlü bir araçtır ve esas olarak dogösterimi kullanarak bir veri yapısı oluşturmanıza ve ardından farklı işlevlerle hesaplamayı etkileyen bir tarafa daraltmanıza izin verir . Ancak güzellik şu ki, bu işlevler sadece veri yapısı üzerinde çalışır ve hepsini nasıl yaptığınızın gerçekten farkında değildir. Tercüman örneğiniz için bunu öneririm.


Kategori uygulama yok gibi görünüyor ($). Oklar ilk bakışta büyük bir abartılı gibi görünüyor, ama yine de ArrowApplyumut verici geliyor - bir şey sağlamam gerekmediği sürece sorun olmayabilir. Daha fazla kontrol yapmak için şu an için +1.
Steve314

3
@ Steve314 Kategoriler uygulamadan yoksun, ancak monadlar onları çalıştırmak için evrensel bir yoldan yoksundur, yararlı olmadıkları anlamına gelmez
Daniel Gratzer

Ben neden kullanamazsınız ortak sebebi var Applicativeya Arrow(ya Monad) - benim tipim değerleri çünkü (genel olarak) normal işlevini kaydıramazsınız temsil bir işlev ancak temsil edilen verilerle ve keyfi işlevleri desteklemez eğer varsa tercüme etmenin bir yolu vardı. Bunun anlamı ben sağlayamaz pure, arrya returnörnekleri için. BTW - bu sınıflar faydalıdır, ancak bunları bu amaç için kullanamam. Arrow"kitlesel taşkınlık" değil - bu, kağıdı anlamaya çalıştığım son andan itibaren anlamaya hazır olmadığım zamandan beri yanlış bir izlenimdi.
Steve314

@ Steve314 Veri oluşturmak için bir monad arabirimi sağlama fikri, ücretsiz
monadların

Haskell Exchange 2013'ten video izledim - Andres Löh kesinlikle iyi açıklıyor, yine de muhtemelen tekrar izlemem, teknikle oynamam vs. gerekiyor. Ama burada gerekli olduğundan emin değilim. Amacım, bir işlev olmayan (ancak bir yorumlayıcı işlevi olan) bir temsili kullanarak bir işlevin soyutlanmasını sağlamaktır. Yan etkileri soyutlamaya ihtiyacım yok ve sıralama işlemleri için temiz bir gösterime ihtiyacım yok. Bu işlev soyutlaması kullanıldıktan sonra, uygulamalar ve kompozisyonlar bir kerede başka bir kütüphanedeki bir algoritma içinde yapılacaktır.
Steve314

2

Belirttiğiniz gibi, burada Uygulayıcı'yı kullanmanın ana sorunu, mantıklı bir tanım olmamasıdır pure. Böylece Applyicat edildi. En azından benim anlayışım bu.

Ne yazık ki, bunun örnekleri de elimde Applydeğil Applicative. Bunun doğru olduğu iddia ediliyor IntMap, ancak neden olduğu hakkında hiçbir fikrim yok. Benzer şekilde, örnek - uzaklık tamsayılarının - bir Applyörneği kabul edip etmediğini bilmiyorum .


bu daha çok bir yorum gibi okuyor, bkz. Nasıl Cevap
Verilir

Afedersiniz. Bu aşağı yukarı ilk cevabım.
user185657

Cevabı geliştirmemi nasıl önerirsiniz?
user185657

okuyucuların cevabınızın sorulan soruya nasıl yanıt verdiğini görmelerine yardımcı olmak için düzenlemeyi düşünün , "neden işlevler için bir tip sınıfı yok? Sadece" olmadığı için "ya da" düşündüğünüz kadar kullanışlı olmadığı için mi? " bu fikrin temel bir sorunu var mı? "
gnat

1
Umarım bu daha iyidir
user185657

1

Bahsedilen ek olarak Category, Arrowve Applicative:

Data.LambdaConal Elliott tarafından da keşfettim :

Lambda benzeri yapıya sahip bazı işlev benzeri sınıflar

İlginç görünüyor, ama örnekler olmadan anlaşılması zor ...

Örnekler

Wiki sayfasında , kütüphane oluşturulmasına neden olan şeylerden biri gibi görünen somut değerler (TV) hakkında örnekler bulunabilir TypeCompose; bkz. Girişler ve fonksiyon değerli çıkışlar .

TV kütüphanesinin fikri Haskell değerlerini (fonksiyonlar dahil) somut bir şekilde göstermektir.

Çıplak lonks göndermeme konusunda StackOverflow kuralını takip etmek için, aşağıdaki şeyler hakkında fikir vermesi gereken bazı bitleri kopyalarım:

İlk örnek şu şekildedir:

apples, bananas :: CInput Int
apples  = iTitle "apples"  defaultIn
bananas = iTitle "bananas" defaultIn

shoppingO :: COutput (Int -> Int -> Int)
shoppingO = oTitle "shopping list" $
            oLambda apples (oLambda bananas total)

shopping :: CTV (Int -> Int -> Int)
shopping = tv shoppingO (+)

şu şekilde çalıştırıldığında verir runIO shopping(daha fazla yorum, GUI ve daha fazla örnek için buraya bakın):

shopping list: apples: 8
bananas: 5
total: 13

bu soruyu sordu nasıl? bkz. Nasıl Cevap
Verilir

@gnat Data.LambdaFonksiyonel şeyler için (istenen) sınıfları tanımlamanın ... Bu şeylerin nasıl kullanılacağından emin değildim. Bunu biraz araştırdım. Muhtemelen, fonksiyon uygulaması için bir soyutlama sağlamazlar.
imz - Ivan Zakharyaschev
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.