Haskell Bytestrings: как сопоставить шаблон?

Я новичок в Haskell, и у меня возникли проблемы с выяснением того, как сопоставить шаблон с ByteString. Версия [Char] моей функции выглядит так:

dropAB :: String -> String
dropAB []       = []
dropAB (x:[])   = x:[]
dropAB (x:y:xs) = if x=='a' && y=='b'
                  then dropAB xs
                  else x:(dropAB $ y:xs) 

Как и ожидалось, это отфильтровывает все вхождения «ab» в строку. Однако у меня проблемы с попыткой применить это к ByteString.

Наивная версия

dropR :: BS.ByteString -> BS.ByteString
dropR []         = []
dropR (x:[])     = [x]
<...>

урожаи

Couldn't match expected type `BS.ByteString'
       against inferred type `[a]'
In the pattern: []
In the definition of `dropR': dropR [] = []

[] явно виноват, так как это обычный String, а не ByteString. Подстановка в BS.empty кажется правильной, но дает «Полное имя в позиции привязки: BS.empty». Оставив нас, чтобы попробовать

dropR :: BS.ByteString -> BS.ByteString
dropR empty              = empty        
dropR (x cons empty)     = x cons empty
<...>

это дает «ошибку синтаксического анализа в шаблоне» для (x cons empty). Я действительно не знаю, что еще я могу сделать здесь.

В качестве примечания: то, что я пытаюсь сделать с помощью этой функции, - это отфильтровать определенный символ UTF16 из некоторого текста. Если есть чистый способ сделать это, я был бы рад услышать об этом, но эта ошибка сопоставления с образцом кажется чем-то, что действительно должен понять начинающий хаскелер.


person LOS    schedule 29.10.2010    source источник
comment
Я не уверен, но, может быть, охранники, а не сопоставление с образцом?   -  person li.davidm    schedule 30.10.2010
comment
Вы не можете отфильтровать символ UTF-16. Возможно, вы имели в виду отфильтровать символ текста, закодированного в UTF-16.   -  person gawi    schedule 30.10.2010
comment
Для потомков, поскольку ОП спросил: подумайте, действительно вам нужно обрабатывать свой ByteString, как если бы это был связанный список. В этом случае decodeUtf16LE и Text.filter (/= '消') являются более чистыми высокоуровневыми инструментами. для достижения того, что пытается сделать LOS. Какой бы случай не привел вас к этому вопросу, у него может быть похожее решение!   -  person Lynn    schedule 26.03.2019


Ответы (5)


Вы можете использовать шаблоны просмотра для таких вещей.

{-# LANGUAGE ViewPatterns #-}    
import Data.ByteString (ByteString, cons, uncons, singleton, empty)
import Data.ByteString.Internal (c2w) 

dropR :: ByteString -> ByteString
dropR (uncons -> Nothing) = empty
dropR (uncons -> Just (x,uncons -> Nothing)) = singleton x
dropR (uncons -> Just (x,uncons -> Just(y,xs))) =
    if x == c2w 'a' && y == c2w 'b'
    then dropR xs
    else cons x (dropR $ cons y xs)
person Ed'ka    schedule 30.10.2010
comment
байтовые строки делают код Haskell таким уродливым; вся элегантность прелюдии String как будто уходит :( - person mntk123; 18.09.2015
comment
@ mntk123 Строки Haskell представляют собой списки ссылок символов и довольно неэффективны. Они все еще существуют для обратной совместимости. Пакет Bytestring и Text предлагают гораздо более мощные решения той же проблемы. - person Jaseem; 01.11.2016

В последней версии GHC (7.8) есть функция, называемая синонимами шаблонов, которую можно добавить к примеру gawi:

{-# LANGUAGE ViewPatterns, PatternSynonyms #-}

import Data.ByteString (ByteString, cons, uncons, singleton, empty)
import Data.ByteString.Internal (c2w)

infixr 5 :<

pattern b :< bs <- (uncons -> Just (b, bs))
pattern Empty   <- (uncons -> Nothing)

dropR :: ByteString -> ByteString
dropR Empty          = empty
dropR (x :< Empty)   = singleton x
dropR (x :< y :< xs)
  | x == c2w 'a' && y == c2w 'b' = dropR xs
  | otherwise                    = cons x (dropR (cons y xs))

Идя дальше, вы можете абстрагировать это для работы с любым классом типов (это будет выглядеть лучше, когда/если мы получим ассоциированные синонимы шаблонов). Определения шаблонов остаются прежними:

{-# LANGUAGE ViewPatterns, PatternSynonyms, TypeFamilies #-}

import qualified Data.ByteString as BS
import Data.ByteString (ByteString, singleton)
import Data.ByteString.Internal (c2w)
import Data.Word

class ListLike l where
  type Elem l

  empty  :: l
  uncons :: l -> Maybe (Elem l, l)
  cons   :: Elem l -> l -> l

instance ListLike ByteString where
  type Elem ByteString = Word8

  empty  = BS.empty
  uncons = BS.uncons
  cons   = BS.cons

instance ListLike [a] where
  type Elem [a] = a

  empty         = []
  uncons []     = Nothing
  uncons (x:xs) = Just (x, xs)
  cons          = (:)

в этом случае dropR может работать как с [Word8], так и с ByteString:

-- dropR :: [Word8]    -> [Word8]
-- dropR :: ByteString -> ByteString
dropR :: (ListLike l, Elem l ~ Word8) => l -> l
dropR Empty          = empty
dropR (x :< Empty)   = cons x empty
dropR (x :< y :< xs)
  | x == c2w 'a' && y == c2w 'b' = dropR xs
  | otherwise                    = cons x (dropR (cons y xs))

И черт возьми:

import Data.ByteString.Internal (w2c)

infixr 5 :•    
pattern b :• bs <- (w2c -> b) :< bs

dropR :: (ListLike l, Elem l ~ Word8) => l -> l
dropR Empty              = empty
dropR (x   :< Empty)     = cons x empty
dropR ('a' :• 'b' :• xs) = dropR xs
dropR (x   :< y   :< xs) = cons x (dropR (cons y xs))

Вы можете увидеть больше в моем сообщении о синонимах шаблонов.

person Iceland_jack    schedule 23.05.2014

Шаблоны используют конструкторы данных. http://book.realworldhaskell.org/read/defining-types-streamlining-functions.html

Ваш empty это просто привязка к первому параметру, мог бы быть x и ничего бы не изменил.

Вы не можете ссылаться на обычную функцию в своем шаблоне, поэтому (x cons empty) недопустимо. Примечание: я думаю, что (cons x empty) это действительно то, что вы имели в виду, но это также незаконно.

ByteString сильно отличается от String. String — это псевдоним [Char], поэтому это настоящий список, а оператор : можно использовать в шаблонах.

ByteString равен Data.ByteString.Internal.PS !(GHC.ForeignPtr.ForeignPtr GHC.Word.Word8) !Int !Int (т.е. указатель на собственный char* + смещение + длина). Поскольку конструктор данных ByteString скрыт, для доступа к данным необходимо использовать функции, а не шаблоны.


Вот решение (конечно, не самое лучшее) вашей проблемы с фильтром UTF-16 с использованием пакета text:

module Test where

import Data.ByteString as BS
import Data.Text as T
import Data.Text.IO as TIO
import Data.Text.Encoding

removeAll :: Char -> Text -> Text
removeAll c t =  T.filter (/= c) t

main = do
  bytes <- BS.readFile "test.txt"
  TIO.putStr $ removeAll 'c' (decodeUtf16LE bytes)
person gawi    schedule 30.10.2010
comment
Немного не знал о шаблонах и конструкторах данных. Поскольку, как отмечено ниже, ByteString не экспортирует свои конструкторы, теперь это имеет смысл. Спасибо всем, кто ответил. - person LOS; 31.10.2010

Для этого я бы сопоставил результат uncons :: ByteString -> Maybe (Word8, ByteString) с образцом.

Сопоставление с образцом в Haskell работает только с конструкторами, объявленными с помощью «data» или «newtype». Тип ByteString не экспортирует свои конструкторы, которые вы не можете сопоставить с образцом.

person Antoine Latter    schedule 30.10.2010

Просто для решения сообщения об ошибке, которое вы получили, и что оно означает:

Couldn't match expected type `BS.ByteString'
       against inferred type `[a]'
In the pattern: []
In the definition of `dropR': dropR [] = []

Таким образом, компилятор ожидал, что ваша функция будет иметь тип: BS.ByteString -> BS.ByteString, потому что вы указали этот тип в своей подписи. Тем не менее он вывел (посмотрев на тело вашей функции), что на самом деле функция имеет тип [a] -> [a]. Там есть несоответствие, поэтому компилятор жалуется.

Проблема в том, что вы думаете о (:) и [] как о синтаксическом сахаре, когда на самом деле они просто конструкторы для типа списка (который ОЧЕНЬ отличается от ByteString).

person jberryman    schedule 30.10.2010