Bagaimana saya bisa menggunakan daftar panjang minimum tetap dengan cara yang total dan elegan?

10

Saat ini saya sedang berurusan dengan fungsi yang berjalan seperti ini:

foo = (\(a:b:c:d:e:f:_) -> foobar a b c d e f) . (++ repeat def)

Dengan kata lain, diberikan daftar, ia menggunakan enam elemen pertama untuk sesuatu, dan jika daftar itu kurang dari enam elemen, ia menggunakan defsebagai pengganti untuk yang hilang. Ini total, tetapi potongan-potongan itu tidak (hanya suka map fromJust . filter isJust), jadi saya tidak suka itu. Saya mencoba menulis ulang ini sehingga tidak perlu menggunakan keberpihakan, dan mendapatkan ini:

foo [] = foobar def def def def def def
foo [a] = foobar a def def def def def
foo [a,b] = foobar a b def def def def
foo [a,b,c] = foobar a b c def def def
foo [a,b,c,d] = foobar a b c d def def
foo [a,b,c,d,e] = foobar a b c d e def
foo (a:b:c:d:e:f:_) = foobar a b c d e f

Secara teknis saya melakukan apa yang saya inginkan, tetapi sekarang ini adalah kekacauan yang sangat besar. Bagaimana saya bisa melakukan ini dengan cara yang lebih elegan dan kurang berulang?

Joseph Sible-Reinstate Monica
sumber
2
Mungkin, tulis uncons :: Default a => [a] -> (a,[a])yang defaultnya def. Atau default takeWithDef. Dan / atau pola tampilan / sinonim pola. Ini membutuhkan penulisan beberapa kode pembantu pembantu.
chi
@ Ya, saya pikir itulah yang akan saya lakukan. Jika Anda menjawabnya, saya akan menerimanya.
Joseph Sible-Reinstate Monica
2
Untuk apa nilainya, saya pikir argumen totalitas case xs ++ repeat def of a:b:c:d:e:f:_ -> ...cukup lokal sehingga saya tidak akan berpikir dua kali tentang hanya menggunakannya dan melewatkan semua mesin tambahan jawaban yang ada memperkenalkan. Secara umum, argumen totalitas global (yang melibatkan invarian yang dipelihara di beberapa fungsi panggilan, misalnya) membuat saya gugup.
Daniel Wagner
Sebenarnya takeWithDeftidak dapat digunakan jika mengembalikan daftar reguler, karena kita perlu mencocokkan pola itu: - / Solusi yang tepat adalah apa yang ditulis Daniel di bawah ini dalam jawaban keduanya. unconshanya mendapat elemen pertama, jadi itu tidak berguna.
chi

Jawaban:

8

Dengan menggunakan paket aman , Anda dapat menulis, misalnya:

(!) = atDef def
foo xs = foobar (xs ! 0) (xs ! 1) (xs ! 2) (xs ! 3) (xs ! 4) (xs ! 5)
Daniel Wagner
sumber
6

Ini setidaknya lebih pendek:

foo (a:b:c:d:e:f:_) = foobar a b c d e f
foo xs = foo (xs ++ repeat def)

Anda dapat dengan mudah melihat bahwa polanya sudah lengkap, tetapi sekarang Anda harus berpikir sedikit untuk melihat bahwa polanya selalu berakhir. Jadi saya tidak tahu apakah Anda dapat menganggapnya sebagai peningkatan.

Kalau tidak, kita bisa melakukannya dengan negara monad, meskipun agak berat:

foo = evalState (foobar <$> pop <*> pop <*> pop <*> pop <*> pop <*> pop)
  where
    pop = do xs <- get
             case xs of [] -> pure def
                        y:ys -> put ys >> pure y

Saya juga bisa membayangkan menggunakan tipe aliran infinite seperti

data S a = S a (S a)

karena maka Anda bisa membangun foodari repeat :: a -> S a, prepend :: [a] -> S a -> S adan take6 :: S a -> (a,a,a,a,a,a), yang semuanya bisa Total. Mungkin tidak sepadan jika Anda belum memiliki tipe seperti itu.

David Fletcher
sumber
3
Oh, saya sangat menyukai ide aliran. Dengan konstruktor infix seperti data S a = a :- S a; infixr 5 :-itu terlihat cukup bersih; foo xs = case prepend xs (repeat def) of a:-b:-c:-d:-e:-f:-_ -> foobar a b c d e f.
Daniel Wagner
4

Hanya untuk bersenang-senang (dan tidak disarankan, ini untuk bersenang-senang), inilah cara lain:

import Data.Default

data Cons f a = a :- f a
infixr 5 :-

data Nil a = Nil -- or use Proxy

class TakeDef f where takeDef :: Default a => [a] -> f a
instance TakeDef Nil where takeDef _ = Nil
instance TakeDef f => TakeDef (Cons f) where
    takeDef (x:xs) = x :- takeDef xs
    takeDef xs = def :- takeDef xs

foo xs = case takeDef xs of
    a:-b:-c:-d:-e:-f:-Nil -> foobar a b c d e f

Jenis yang Anda gunakan dalam pencocokan pola sama dengan melewatkan tingkat-jenis alami untuk takeDefmengatakan berapa banyak elemen yang harus dilihat.

Daniel Wagner
sumber
1
Ini adalah pendekatan yang saya sukai sejauh ini. Saya mungkin akan menggunakan pola tampilan untuk melengkapinya. (Kenapa "tidak direkomendasikan"? Apa kontra?)
chi
3
Ini mewujudkan dengan tepat apa yang mulai salah ketika Anda berinvestasi besar-besaran dalam pemrograman tipe-level: apa yang merupakan program satu-baris, yang langsung dapat dipahami menjadi sepuluh baris yang mengharuskan pembaca untuk secara serius menggunakan mesin inferensi tipe mental mereka.
Daniel Wagner
1
Saya mengerti maksud Anda. Saya menghitung foo (takeDef -> a:-b:-c:-d:-e:-f:-Nil) -> foobar a b c d e fsebagai satu baris. Saya tidak menghitung sisanya karena ini adalah kode yang seharusnya ada di perpustakaan, untuk digunakan kembali. Jika harus ditulis hanya untuk kasus ini, itu jelas berlebihan seperti yang Anda katakan.
chi