Apakah menerapkan fungsi kata-kata ini mungkin tanpa langkah pascaproses setelah melipat?

14

Real World Haskell, bab 4, halaman 98 dari cetakan bertanya apakah wordsdapat diimplementasikan menggunakan lipatan, dan ini juga pertanyaan saya:

Apa itu mungkin? Jika tidak, mengapa? Jika ya, bagaimana?

Saya datang dengan berikut ini, yang didasarkan pada gagasan bahwa setiap non-spasi harus ditambahkan ke kata terakhir dalam daftar output (ini terjadi di otherwisepenjaga), dan bahwa ruang harus memicu penambahan kata emtpy ke daftar keluaran jika belum ada (ini ditangani di if- then- else).

myWords :: String -> [String]
myWords = foldr step [[]]
  where
    step x yss@(y:ys)
      | x == ' ' = if y == "" then yss else "":yss
      | otherwise = (x:y):ys

Jelas solusi ini salah, karena spasi spasi pada string input menghasilkan satu string kosong terkemuka di daftar output string.

Pada tautan di atas, saya telah melihat ke beberapa solusi yang diusulkan untuk pembaca lain, dan banyak dari mereka bekerja mirip dengan solusi saya, tetapi mereka umumnya "pasca-proses" output flip, misalnya dengan tailmemasukkannya jika ada adalah string terkemuka yang kosong.

Pendekatan lain menggunakan tupel (sebenarnya hanya berpasangan), sehingga lipatan berurusan dengan pasangan dan dapat dengan baik menangani ruang terkemuka / trailing.

Dalam semua pendekatan ini, foldr(atau lipatan lain, fwiw) bukan fungsi yang memberikan hasil akhir dari kotak; selalu ada hal lain dengan harus menyesuaikan output entah bagaimana.

Oleh karena itu saya kembali ke pertanyaan awal dan bertanya apakah itu benar-benar mungkin untuk diterapkan words(dengan cara yang benar menangani trailing / spasi / spasi berulang) menggunakan lipatan. Dengan menggunakan lipatan, maksud saya bahwa fungsi lipatan harus merupakan fungsi terluar:

myWords :: String -> [String]
myWords input = foldr step seed input
Enrico Maria De Angelis
sumber

Jawaban:

13

Jika saya mengerti dengan benar, persyaratan Anda termasuk

(1) words "a b c" == words " a b c" == ["a", "b", "c"]
(2) words "xa b c" == ["xa", "b", "c"] /= ["x", "a", "b", "c"] == words "x a b c"

Ini menyiratkan bahwa kita tidak dapat memiliki

words = foldr step base

untuk apa saja stepdan base.

Memang, jika kita memilikinya, maka

words "xa b c"
= def words and foldr
step 'x' (words "a b c")
= (1)
step 'x' (words " a b c")
= def words and foldr
words "x a b c"

dan ini bertentangan (2).

Anda pasti perlu beberapa post-processing setelah foldr.

chi
sumber
1
Saya lebih menyukai bahasa ini ...
Enrico Maria De Angelis
Atau bahkan ["xa"] == words "xa" == step 'x' (words "a") == step 'x' (words " a") == words "x a" == ["x", "a"], yang memiliki manfaat menjadi argumen yang valid untuk kedua arah lipatan
Cireo
5

@chi memiliki argumen yang bagus yang tidak dapat Anda terapkan wordsdengan menggunakan lipatan "a", tetapi Anda mengatakan menggunakan lipatan s .

words = filterNull . words1
    where
    filterNull = foldr (\xs -> if null xs then id else (xs:)) []
    words1 = foldr (\c -> if c == ' ' then ([]:) else consHead c) []
    consHead c []       = [[c]]
    consHead c (xs:xss) = (c:xs):xss

Baik fungsi terluar maupun terdalam adalah lipatan. ;-)

luqui
sumber
Saya pikir Anda tahu apa yang saya maksud, tetapi +1 karena pilih-pilih: P
Enrico Maria De Angelis
1

Iya. Meskipun sedikit rumit, Anda masih dapat melakukan pekerjaan ini dengan benar dengan menggunakan satu foldrdan tidak ada yang lain jika Anda tinggal di CPS ( Continuation Passing Style ). Saya telah menunjukkan jenis chunksOffungsi khusus sebelumnya.

Dalam jenis lipatan akumulator kami, maka hasil lipatan adalah fungsi dan kami harus menerapkannya pada jenis input identitas sehingga kami mendapatkan hasil akhir. Jadi ini dapat dihitung sebagai tahap pemrosesan akhir atau tidak karena kita menggunakan lipatan tunggal di sini dan jenisnya termasuk fungsi. Terbuka untuk diperdebatkan :)

ws :: String -> [String]
ws str = foldr go sf str $ ""
         where
         sf :: String -> [String]
         sf s = if s == " " then [""] else [s]
         go :: Char -> (String -> [String]) -> (String -> [String])
         go c f = \pc -> let (s:ss) = f [c]
                         in case pc of
                            ""        -> dropWhile (== "") (s:ss)
                            otherwise -> case (pc == " ", s == "") of
                                         (True, False)  -> "":s:ss
                                         (True, True)   -> s:ss
                                         otherwise      -> (pc++s):ss

λ> ws "   a  b    c   "
["a","b","c"]

sf : Nilai fungsi awal untuk memulai.

go : Fungsi iterator

Kami sebenarnya tidak sepenuhnya memanfaatkan kekuatan CPS di sini karena kami memiliki karakter sebelumnya pcdan karakter arus yang cada di setiap kesempatan. Itu sangat berguna dalam chunksOffungsi tersebut di atas sementara chunking sebuah [Int]ke [[Int]]setiap kali urutan menaik elemen yang rusak.

Redu
sumber