Dapatkah saya mencetak di Haskell jenis fungsi polimorfik seperti yang akan terjadi jika saya meneruskannya ke entitas jenis konkret?

Dec 12 2020

Berikut adalah fungsi polimorfik dalam 3 jenis:

:t (.)
(.) :: (b -> c) -> (a -> b) -> a -> c

dan di sini fungsi non polimorfik:

:t Data.Char.digitToInt
Data.Char.digitToInt :: Char -> Int

Jika kita menerapkan yang pertama ke yang terakhir, kita mendapatkan fungsi polimorfik dalam 1 tipe:

:t (.) Data.Char.digitToInt
(.) Data.Char.digitToInt :: (a -> Char) -> a -> Int

yang artinya (.)itu "dipakai" (saya tidak yakin ini istilah yang benar; sebagai programmer C ++, saya akan menyebutnya begitu) dengan b === Chardan c === Int, jadi tanda tangan dari (.)yang diterapkan digitToIntadalah sebagai berikut

(Char -> Int) -> (a -> Char) -> a -> Int

Pertanyaan saya adalah: adakah cara agar tanda tangan ini dicetak di layar, diberikan (.), digitToIntdan "informasi" yang ingin saya terapkan yang pertama ke yang terakhir?

Bagi yang tertarik, pertanyaan ini sebelumnya ditutup sebagai duplikat dari pertanyaan ini .

Jawaban

7 MikeSpivey Dec 13 2020 at 06:32

Jawaban lain memerlukan bantuan fungsi yang telah ditentukan dengan tipe yang dibatasi secara artifisial, seperti asTypeOffungsi dalam jawaban dari HTNW. Ini tidak perlu, karena interaksi berikut menunjukkan:

Prelude> let asAppliedTo f x = const f (f x)

Prelude> :t head `asAppliedTo` "x"
head `asAppliedTo` "x" :: [Char] -> Char

Prelude> :t (.) `asAppliedTo` Data.Char.digitToInt
(.) `asAppliedTo` Data.Char.digitToInt
  :: (Char -> Int) -> (a -> Char) -> a -> Int

Ini mengeksploitasi kurangnya polimorfisme dalam pengikatan lambda yang tersirat dalam definisi asAppliedTo. Kedua kemunculan fdalam tubuhnya harus diberi jenis yang sama, dan itulah jenis akibatnya. Fungsi yang constdigunakan di sini juga memiliki tipe alaminya a -> b -> a:

const x y = x
12 HTNW Dec 12 2020 at 09:09

Ada fungsi kecil rapi yang tersembunyi di sudut Prelude:

Prelude.asTypeOf :: a -> a -> a
asTypeOf x _ = x

Ini didokumentasikan sebagai "memaksa argumen pertamanya memiliki tipe yang sama dengan yang kedua." Kita bisa menggunakan ini untuk memaksa tipe dari (.)argumen pertama:

-- (.) = \x -> (.) x = \x -> (.) $ x `asTypeOf` Data.Char.digitToInt -- eta expansion followed by definition of asTypeOf -- the RHS is just (.), but restricted to arguments with the same type as digitToInt -- "what is the type of (.) when the first argument is (of the same type as) digitToInt?" ghci> :t \x -> (.) $ x `asTypeOf` Data.Char.digitToInt
\x -> (.) $ x `asTypeOf` Data.Char.digitToInt
  :: (Char -> Int) -> (a -> Char) -> a -> Int

Tentu saja, ini berfungsi untuk argumen sebanyak yang Anda butuhkan.

ghci> :t \x y -> (x `asTypeOf` Data.Char.digitToInt) . (y `asTypeOf` head)
\x y -> (x `asTypeOf` Data.Char.digitToInt) . (y `asTypeOf` head)
  :: (Char -> Int) -> ([Char] -> Char) -> [Char] -> Int

Anda dapat menganggap ini sebagai variasi dari ide @ KABuhr di komentar — menggunakan fungsi dengan tanda tangan yang lebih membatasi daripada implementasinya untuk memandu jenis inferensi — kecuali kita tidak harus mendefinisikan apa pun sendiri, dengan konsekuensi karena tidak bisa hanya salin ekspresi yang dimaksud di bawah lambda.

8 K.A.Buhr Dec 12 2020 at 09:57

Saya pikir jawaban @ HTNW mungkin mencakupnya, tetapi untuk kelengkapan, berikut cara kerja inContextsolusinya secara detail.

Jenis tanda tangan dari fungsi tersebut:

inContext :: a -> (a -> b) -> a

artinya, jika Anda memiliki sesuatu yang ingin Anda ketik, dan "konteks" yang digunakan (diekspresikan sebagai lambda yang menganggapnya sebagai argumen), katakan dengan tipe:

thing :: a1
context :: a2 -> b

Anda dapat memaksa penyatuan a1(tipe umum thing) dengan a2(batasan konteks) hanya dengan membuat ekspresi:

thing `inContext` context

Biasanya, tipe terpadu thing :: aakan hilang, tetapi tipe tanda tangan inContextmenyiratkan bahwa tipe dari keseluruhan ekspresi yang dihasilkan ini juga akan disatukan dengan tipe yang diinginkan a, dan GHCi akan dengan senang hati memberi tahu Anda tipe ekspresi itu.

Jadi ungkapannya:

(.) `inContext` \hole -> hole digitToInt

akhirnya mendapatkan jenis yang (.)akan memiliki dalam konteks yang ditentukan. Anda dapat menulis ini, agak menyesatkan, sebagai:

(.) `inContext` \(.) -> (.) digitToInt

karena (.)sama bagusnya dengan nama argumen untuk lambda anonim hole. Ini berpotensi membingungkan, karena kita membuat pengikatan lokal yang membayangi definisi tingkat atas (.), tetapi masih menamai hal yang sama (dengan tipe yang disempurnakan), dan penyalahgunaan lambda ini memungkinkan kita untuk menulis ekspresi asli (.) digitToIntsecara verbatim, dengan boilerplate yang sesuai.

Sebenarnya tidak relevan bagaimana inContextdidefinisikan, jika Anda hanya meminta GHCi untuk tipenya, itu inContext = undefinedakan berhasil. Tapi, hanya dengan melihat tipe tanda tangan, cukup mudah untuk memberikan inContextdefinisi yang berfungsi:

inContext :: a -> (a -> b) -> a
inContext a _ = a

Ternyata ini hanya definisi const, jadi inContext = constberfungsi juga.

Anda dapat menggunakan inContextuntuk mengetik beberapa hal sekaligus, dan itu bisa berupa ekspresi, bukan nama. Untuk mengakomodasi yang pertama, Anda dapat menggunakan tupel; agar yang terakhir berfungsi, Anda harus menggunakan nama argumen yang lebih masuk akal di lambas Anda.

Misalnya:

λ> :t (fromJust, fmap length) `inContext` \(a,b) -> a . b
(fromJust, fmap length) `inContext` \(a,b) -> a . b
  :: Foldable t => (Maybe Int -> Int, Maybe (t a) -> Maybe Int)

memberi tahu Anda bahwa dalam ekspresi fromJust . fmap length, tipe telah dikhususkan untuk:

fromJust :: Maybe Int -> Int
fmap length :: Foldable t => Maybe (t a) -> Maybe Int
6 FyodorSoikin Dec 12 2020 at 03:35

Anda dapat melakukannya menggunakan TypeApplicationsekstensi, yang memungkinkan Anda untuk secara eksplisit menentukan tipe yang ingin Anda gunakan untuk membuat contoh parameter tipe:

λ :set -XTypeApplications                                 
λ :t (.) @Char @Int
(.) @Char @Int :: (Char -> Int) -> (a -> Char) -> a -> Int

Perhatikan bahwa argumen harus dalam urutan yang tepat.

Untuk fungsi yang memiliki jenis tanda tangan "biasa" foo :: a -> b, urutan ditentukan oleh urutan parameter jenis pertama kali muncul dalam tanda tangan.

Untuk fungsi yang menggunakan ExplicitForalllike foo :: forall b a. a -> b, urutannya ditentukan oleh apa pun isinya forall.


Jika Anda ingin mengetahui jenis khusus berdasarkan menerapkan (.)ke digitToChar(sebagai lawan hanya mengetahui yang jenis untuk mengisi), aku cukup yakin Anda tidak bisa di GHCi, tapi aku bisa sangat merekomendasikan dukungan Haskell IDE.

Misalnya, berikut ini tampilan saya di VSCode (inilah ekstensinya ):

5 chi Dec 12 2020 at 16:39

Ini adalah variasi kecil dari jawaban HTNW.

Misalkan kita memiliki ekspresi yang berpotensi besar yang melibatkan pengenal polimorfik poly

 .... poly ....

dan kami bertanya-tanya bagaimana tipe polimorfik dipakai pada saat itu.

Ini dapat dilakukan dengan memanfaatkan dua fitur GHC: asTypeOf(seperti yang disebutkan oleh HTNW) dan lubang yang diketik , sebagai berikut:

 .... (poly `asTypeOf` _) ....

Setelah membaca _lubang, GHC akan menghasilkan kesalahan yang melaporkan jenis istilah yang harus dimasukkan untuk menggantikan lubang itu. Sejak kami menggunakan asTypeOf, ini harus sama dengan jenis contoh khusus yang polykami butuhkan dalam konteks itu.

Berikut contoh di GHCi:

> ((.) `asTypeOf` _) Data.Char.digitToInt
<interactive>:11:17: error:
    * Found hole: _ :: (Char -> Int) -> (a -> Char) -> a -> Int