Mengapa penerapan ini merupakan contoh yang buruk dari Kelas Jenis Lipat?
Saya sedang mengerjakan Buku Haskell yang luar biasa . Di akhir bab Traversable (21), saya perlu menulis sebuah instance untuk Tree berikut:
data Tree a =
Empty
| Leaf a
| Node (Tree a) a (Tree a)
Berikut ini tautan ke kode lengkap solusi saya. Latihan merekomendasikan untuk mencoba menerapkan keduanya foldMap
dan foldr
. Beginilah cara saya menerapkan foldr
(tanpa banyak memikirkan urutan pemanggilan):
foldr _ z Empty = z
foldr f z (Leaf x) = f x z
foldr f z (Node left x right) =
f x $ foldr f (foldr f z left) right
Saya kemudian menerapkan foldMap
sebagai berikut:
foldMap f Empty = mempty
foldMap f (Leaf x) = f x
foldMap f (Node left x right) =
foldMap f left <> f x <> foldMap f right
Ketika saya menjalankan kelompok foldable
uji QuickCheck , saya mengalami beberapa kegagalan. Mengubah foldr
implementasi saya menjadi yang berikut membuat semua tes lulus:
foldr _ z Empty = z
foldr f z (Leaf x) = f x z
foldr f z (Node left x right) =
foldr f (f x (foldr f z right)) left
Saya mencoba menjalankan sendiri kasus pengujian yang gagal, tetapi tidak dapat membuat ulang kegagalan:
*Ch21_12_ExercisesTree Data.Monoid> tree = Node (Node (Leaf (-5)) 3 (Node (Leaf 3) 5 Empty)) (-2) Empty
*Ch21_12_ExercisesTree Data.Monoid> foldr (<>) (mempty :: Sum Int) t
Sum {getSum = 4}
*Ch21_12_ExercisesTree Data.Monoid> foldMap Sum t
Sum {getSum = 4}
Saya curiga ada sesuatu yang tidak saya fold
pahami tentang fungsi ing yang digunakan QuickCheck.
Pertanyaan:
- Mengapa kegagalan terjadi?
- Apakah ada cara untuk mendapatkan fungsi yang digunakan dalam pengujian dengan QuickCheck?
Jawaban
foldr
dapat diperoleh foldMap
dengan menggunakan Endomonoid , dengan a -> b -> b
fungsi mengubah a
nilai menjadi b -> b
fungsi yang dapat disusun (secara monoid). Karena itu, jika Anda foldMap
adalah ...
foldMap f Empty = mempty
foldMap f (Leaf x) = f x
foldMap f (Node left x right) =
foldMap f left <> f x <> foldMap f right
... yang sesuai foldr
harus:
foldr f z Empty = id z -- mempty amounts to id
foldr f z (Leaf x) = (f x) z
foldr f z (Node left x right) =
((\e -> foldr f e left) . f x . (\e -> foldr f e right)) z -- (<>) amounts to (.)
Jika kita merapikannya sedikit ...
foldr f z Empty = z
foldr f z (Leaf x) = f x z
foldr f z (Node left x right) =
foldr f (f x (foldr f z right)) left)
... kami mendapatkan definisi yang benar foldr
seperti yang tertulis dalam pertanyaan Anda. Karena perbedaan antara implementasi berkaitan dengan urutan komposisi, mencoba monoid non-komutatif langsung mengarah ke kasus yang gagal, seperti yang telah Anda ketahui .
Pada subquestion QuickCheck, saya tunduk pada jawaban DDub. .
Seperti yang telah Anda simpulkan, alasan Anda mengalami kegagalan adalah karena kedua implementasi tersebut dapat dibedakan, yang dapat Anda amati dengan menggunakan monoid non-komutatif.
Mendapatkan fungsi yang digunakan dengan quickcheck tidaklah sesederhana itu. Lihat, sebagai contoh, pertanyaan / jawaban tentang Show
fungsi yang dihasilkan oleh pemeriksaan cepat untuk informasi lebih lanjut.
Cara untuk mendapatkan Show
fungsi bisa keluar dari QuickCheck adalah untuk membungkus fungsi di dalam Funjenis . Meskipun demikian, kode yang Anda panggil ( ditemukan di sini ) hanya menggunakan fungsi secara langsung, jadi tidak akan pernah bisa ditampilkan. Salah satu opsi yang dapat Anda coba adalah membuat versi Anda sendiri dari foldable
fungsi di mana Anda menggunakan tipe Fun a b
sebagai pengganti a -> b
dan applyFun
seperlunya untuk menerapkan fungsi.
Saya baru menyadari bahwa saya menggunakan Monoid komutatif ... Saya dapat membuat ulang kegagalan menggunakan Monoid non-komutatif:
> ftree = fmap (First . Just) tree
> foldr (<>) mempty ft
First {getFirst = Just (-2)}
> foldMap (First . Just) ft
First {getFirst = Just (First {getFirst = Just (-5)})}
Ini mungkin kasus sederhana. Saya membayangkan bahwa dalam kode produksi dengan tipe data nyata ini bisa jauh lebih rumit.