Mengapa tidak ada typeclass untuk fungsi?

17

Dalam masalah pembelajaran yang telah saya lakukan, saya menyadari bahwa saya membutuhkan typeclass untuk fungsi dengan operasi untuk menerapkan, menyusun dll. Alasan ...

  1. Akan lebih mudah untuk memperlakukan representasi suatu fungsi seolah-olah itu adalah fungsi itu sendiri, sehingga menerapkan fungsi tersebut secara implisit menggunakan penerjemah, dan menyusun fungsi memperoleh deskripsi baru.

  2. Setelah Anda memiliki typeclass untuk fungsi, Anda dapat memiliki typeclasses untuk jenis fungsi khusus - dalam kasus saya, saya ingin fungsi yang dapat dibalik.

Misalnya, fungsi yang menerapkan offset bilangan bulat dapat diwakili oleh ADT yang berisi bilangan bulat. Menerapkan fungsi-fungsi itu hanya berarti menambahkan integer. Komposisi diimplementasikan dengan menambahkan bilangan bulat yang dibungkus. Fungsi terbalik memiliki bilangan bulat dinegasikan. Fungsi identitas membungkus nol. Fungsi konstan tidak dapat disediakan karena tidak ada representasi yang cocok untuk itu.

Tentu saja tidak perlu mengeja sesuatu seolah-olah nilainya adalah fungsi Haskell asli, tapi begitu saya punya ide, saya pikir perpustakaan seperti itu pasti sudah ada dan mungkin bahkan menggunakan ejaan standar. Tapi saya tidak dapat menemukan typeclass di perpustakaan Haskell.

Saya menemukan modul Data.Function , tetapi tidak ada typeclass - hanya beberapa fungsi umum yang juga tersedia dari Prelude.

Jadi - mengapa tidak ada typeclass untuk fungsi? Apakah itu "hanya karena tidak ada" atau "karena itu tidak berguna seperti yang Anda pikirkan"? Atau mungkin ada masalah mendasar dengan gagasan itu?

Masalah terbesar yang mungkin saya pikirkan sejauh ini adalah bahwa aplikasi fungsi pada fungsi aktual mungkin harus dibuat secara khusus oleh kompiler untuk menghindari masalah perulangan - untuk menerapkan fungsi ini saya perlu menerapkan fungsi aplikasi fungsi, dan untuk melakukan itu saya perlu memanggil fungsi aplikasi fungsi, dan untuk melakukan itu ...

Petunjuk lainnya

Kode contoh untuk menunjukkan apa yang saya tuju ...

{-# 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)

Aplikasi ini melibatkan semacam penyatuan di mana nilai-nilai terpadu tidak sama, tetapi terkait melalui fungsi-fungsi yang tidak dapat dibalik - Logika gaya prolog tetapi dengan a = f(b)kendala daripada a = b. Sebagian besar komposisi akan dihasilkan dari pengoptimalan struktur penemuan-serikat. Kebutuhan akan invers harus jelas.

Jika tidak ada item dalam himpunan terpadu memiliki nilai yang tepat, maka item tertentu hanya dapat dikuantifikasi relatif terhadap item lain dalam himpunan terpadu tersebut. Itu sebabnya saya tidak ingin menggunakan fungsi "nyata" - menghitung nilai-nilai relatif itu. Saya dapat menghilangkan seluruh aspek fungsi dan hanya memiliki jumlah absolut dan relatif - Saya mungkin hanya perlu angka / vektor dan (+)- tetapi astronot arsitektur batin saya menginginkan kesenangannya.

Satu-satunya cara saya memecah tautan lagi adalah melalui backtracking, dan semuanya murni - union-find akan dilakukan menggunakan kunci menjadi IntMap"pointer". Saya memiliki fungsi union-find yang sederhana, tetapi karena saya belum menambahkan fungsi yang tidak dapat dibalik, tidak ada gunanya mencantumkannya di sini.

Alasan saya tidak bisa menggunakan Applicative, Monad, Arrow dll

Operasi utama yang saya butuhkan untuk kelas abstraksi fungsi adalah aplikasi dan komposisi. Itu terdengar akrab - misalnya Applicative (<*>), Monad (>>=)dan Arrow (>>>)semua fungsi komposisi. Namun, tipe yang mengimplementasikan abstraksi fungsi dalam kasus saya akan berisi beberapa struktur data yang mewakili suatu fungsi, tetapi yang tidak (dan tidak dapat berisi) suatu fungsi, dan yang hanya dapat mewakili beberapa set fungsi yang terbatas.

Seperti yang saya sebutkan dalam penjelasan kode, kadang-kadang saya hanya dapat mengukur satu item relatif terhadap yang lain karena tidak ada item dalam "unified" cluster memiliki nilai yang tepat. Saya ingin dapat memperoleh representasi dari fungsi itu, yang secara umum akan menjadi komposisi dari beberapa fungsi yang disediakan (berjalan ke leluhur bersama di pohon penyatuan / menemukan) dan beberapa fungsi terbalik (berjalan kembali ke yang lain barang).

Kasus sederhana - di mana "fungsi" asli terbatas pada "fungsi" integer-offset, saya ingin hasil yang dikomposisikan sebagai "fungsi" integer-offset - tambahkan komponen offset. Itu adalah bagian besar dari mengapa fungsi komposisi perlu di kelas serta fungsi aplikasi.

Ini berarti saya tidak dapat menyediakan operasi pure, returnatau arruntuk tipe saya, jadi saya tidak dapat menggunakan Applicative, Monadatau Arrow.

Ini bukan kegagalan tipe-tipe itu - ini adalah ketidakcocokan abstraksi. Abstraksi yang saya inginkan adalah fungsi murni sederhana. Tidak ada efek samping, misalnya, dan tidak perlu membuat notasi yang nyaman untuk mengurutkan dan menyusun fungsi selain yang setara dengan standar (.) Yang berlaku untuk semua fungsi.

Saya bisa contoh Category. Saya yakin bahwa semua hal fungsional saya akan dapat memberikan identitas, meskipun saya mungkin tidak membutuhkannya. Tetapi karena Categorytidak mendukung aplikasi, saya masih perlu kelas turunan untuk menambahkan operasi itu.

Steve314
sumber
2
Sebut saya gila, tetapi ketika saya memikirkan sebuah typeclass seperti Anda menggambarkan itu untuk menerapkan dan menyusun dll saya pikir functors aplikatif, yang fungsinya. Mungkin itu tipe kelas yang Anda pikirkan?
Jimmy Hoffa
1
Saya tidak berpikir Applicativeitu benar - itu membutuhkan nilai yang akan dibungkus serta fungsi, sedangkan saya hanya ingin membungkus fungsi, dan fungsi yang dibungkus benar-benar fungsi, sedangkan fungsi saya yang dibungkus biasanya tidak akan (dalam kasus yang paling umum, mereka AST yang menggambarkan fungsi). Di mana <*>ada tipe f (a -> b) -> f a -> f b, saya ingin operator aplikasi dengan tipe di g a b -> a -> bmana adan btentukan domain dan codomain dari fungsi yang dibungkus, tetapi apa yang ada di dalam wrapper bukanlah (harus) fungsi yang sebenarnya. Di Arrows - mungkin, saya akan melihatnya.
Steve314
3
jika Anda ingin kebalikannya bukankah itu menyiratkan grup?
jk.
1
@jk. Poin yang bagus, menyadari bahwa ada banyak hal untuk dibaca tentang inverses fungsi yang dapat membuat OP menemukan apa yang dia cari. Berikut ini beberapa bacaan menarik tentang topik ini. Tetapi invers fungsi google untuk haskell memberikan banyak konten bacaan yang aneh. Mungkin dia hanya menginginkan Data.Group
Jimmy Hoffa
2
@ Steve314 Saya pikir fungsi dengan komposisi adalah kategori monoid. Mereka adalah monoid jika domain dan kode domain selalu sama.
Tim Seguine

Jawaban:

14

Ya, saya tidak tahu ada ide-ide yang memasarkan diri mereka sebagai mewakili hal-hal "fungsi-y". Tetapi ada beberapa yang mendekati

Kategori

Jika Anda memiliki konsep fungsi sederhana yang memiliki identitas dan komposisi, daripada Anda memiliki kategori.

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

Kelemahannya adalah bahwa Anda tidak dapat membuat kategori contoh bagus dengan satu set objek ( a, b, dan c). Anda dapat membuat kelas kategori kustom yang saya kira.

Panah

Jika fungsi Anda memiliki gagasan tentang produk dan dapat menyuntikkan fungsi sewenang-wenang, daripada panah untuk Anda

 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 memiliki gagasan tentang aplikasi yang terlihat penting untuk apa yang Anda inginkan.

Pelamar

Pelamar memiliki gagasan aplikasi Anda, saya telah menggunakannya dalam AST untuk mewakili aplikasi fungsi.

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

Ada banyak ide lain. Tetapi tema yang umum adalah untuk membangun beberapa struktur data yang mewakili fungsi Anda, dan kemudian meneruskannya ke fungsi interpretasi.

Ini juga berapa banyak monad gratis yang berfungsi. Saya sarankan menyodok ini jika Anda merasa berani, mereka adalah alat yang ampuh untuk hal-hal yang Anda sarankan dan pada dasarnya membiarkan Anda membangun struktur data menggunakan donotasi dan kemudian runtuh menjadi sisi yang mempengaruhi perhitungan dengan fungsi yang berbeda . Tetapi keindahannya adalah bahwa fungsi-fungsi ini hanya beroperasi pada struktur data, dan tidak benar-benar menyadari bagaimana Anda membuat semuanya. Inilah yang akan saya sarankan untuk contoh Anda seorang penerjemah.

Daniel Gratzer
sumber
Kategori tampaknya kurang aplikasi - ($). Panah terlihat seperti kerja keras yang sangat besar pada pandangan pertama, tetapi masih ArrowApplyterdengar menjanjikan - selama saya tidak perlu memberikan apa pun yang saya tidak bisa, mungkin tidak masalah. +1 untuk saat ini, dengan lebih banyak pemeriksaan yang harus dilakukan.
Steve314
3
@ Steve314 Kategori tidak memiliki aplikasi, tetapi monad tidak memiliki cara universal untuk menjalankannya, tidak berarti mereka tidak berguna
Daniel Gratzer
Ada alasan umum mengapa saya tidak dapat menggunakan Applicativeatau Arrow(atau Monad) - Saya tidak dapat membungkus fungsi normal (secara umum) karena nilai tipe saya mewakili fungsi tetapi diwakili oleh data, dan tidak akan mendukung fungsi sewenang-wenang jika jika ada cara untuk menerjemahkan. Itu berarti saya tidak bisa menyediakan pure, arratau returnuntuk contoh. BTW - kelas-kelas itu berguna tetapi saya tidak bisa menggunakannya untuk tujuan khusus ini. Arrowbukan "pembunuhan besar-besaran" - itu adalah kesan yang salah dari terakhir kali saya mencoba membaca koran, ketika saya tidak siap untuk memahaminya.
Steve314
@ Steve314 Gagasan untuk menyediakan antarmuka monad untuk membangun data adalah apa yang digunakan untuk monad gratis, periksa
Daniel Gratzer
Saya menonton video dari Haskell Exchange 2013 - Andres Löh jelas menjelaskan dengan baik, meskipun saya mungkin masih perlu menontonnya lagi, bermain dengan teknik dll. Tapi saya tidak yakin itu diperlukan di sini. Tujuan saya adalah memiliki abstraksi dari suatu fungsi menggunakan representasi yang bukan merupakan fungsi (tetapi yang memiliki fungsi juru bahasa). Saya tidak memerlukan abstraksi efek samping, dan saya tidak perlu notasi bersih untuk operasi sekuensing. Setelah abstraksi fungsi ini digunakan, aplikasi dan komposisi akan dilakukan satu per satu dalam suatu algoritma di perpustakaan lain.
Steve314
2

Seperti yang Anda tunjukkan, masalah utama dengan menggunakan Aplikasi di sini adalah tidak ada definisi yang masuk akal untuk pure. Oleh karena itu, Applydiciptakan. Setidaknya, itulah pemahaman saya tentang hal itu.

Sayangnya, saya tidak punya contoh di tangan contoh Applyyang tidak juga Applicative. Dikatakan ini memang benar IntMap, tetapi saya tidak tahu mengapa. Demikian pula, saya tidak tahu apakah contoh Anda - offset bilangan bulat - menerima Applycontoh.

pengguna185657
sumber
ini berbunyi lebih seperti komentar, lihat How to Answer
gnat
Maaf. Ini kurang lebih jawaban pertama saya.
user185657
Bagaimana Anda menyarankan saya meningkatkan jawabannya?
user185657
pertimbangkan untuk mengedit untuk membantu pembaca melihat bagaimana jawaban Anda menjawab pertanyaan yang diajukan, "mengapa tidak ada typeclass untuk fungsi? Apakah itu" hanya karena tidak ada "atau" karena itu tidak begitu berguna seperti yang Anda pikirkan "? Atau mungkin ada masalah mendasar dengan gagasan itu? "
nyamuk
1
Saya harap ini lebih baik
user185657
1

Selain disebutkan Category, Arrow, dan Applicative:

Saya juga menemukan Data.Lambdaoleh Conal Elliott:

Beberapa kelas seperti fungsi, memiliki konstruksi seperti lambda

Terlihat menarik, tentu saja, tetapi sulit dimengerti tanpa contoh ...

Contohnya

Contohnya dapat ditemukan di halaman wiki tentang nilai nyata (TV) yang tampaknya menjadi salah satu hal yang menyebabkan penciptaan TypeComposeperpustakaan; lihat Input dan output bernilai fungsi .

Gagasan perpustakaan TV adalah untuk menampilkan nilai Haskell (termasuk fungsi) secara nyata.

Untuk mengikuti aturan StackOverflow tentang tidak memposting bon lonks, saya menyalin beberapa bit di bawah ini yang seharusnya memberikan ide tentang hal-hal ini:

Contoh pertama berbunyi:

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 (+)

yang memberi ketika dijalankan sebagai runIO shopping(lihat di sana untuk lebih banyak komentar, GUI, dan lebih banyak contoh):

shopping list: apples: 8
bananas: 5
total: 13
imz - Ivan Zakharyaschev
sumber
bagaimana ini menjawab pertanyaan yang diajukan? lihat Cara Menjawab
agas
@gnat Saya berpikir bahwa definisi dalam Data.Lambdamemberikan kelas untuk hal-hal seperti fungsi (yang diminta) ... Saya tidak yakin bagaimana hal-hal ini digunakan. Saya sedikit mengeksplorasi ini. Mungkin, mereka tidak menyediakan abstraksi untuk aplikasi fungsi.
imz - Ivan Zakharyaschev