principio - Haskell iteratee: ejemplo simple y simple de eliminar espacios en blanco.
quitar espacios en blanco excel 2016 (1)
Si solo quieres código, es esto:
procFile'' iFile oFile = fileDriver (joinI $
enumLinesBS ><>
mapChunks (map rstrip) $
I.mapM_ (B.appendFile oFile))
iFile
Comentario:
Este es un proceso de tres etapas: primero transformas el flujo en bruto en un flujo de líneas, luego aplicas la función para convertir ese flujo de líneas y finalmente consumes el flujo. Como rstrip
encuentra en la etapa intermedia, creará un transformador de flujo (Enumeratee).
Puede usar mapChunks
o convStream
, pero mapChunks
es más simple. La diferencia es que mapChunks
no le permite cruzar los límites de los fragmentos, mientras que convStream
es más general. Prefiero convStream
porque no expone ninguna de las implementaciones subyacentes, pero si mapChunks
es suficiente, el código resultante suele ser más corto.
rstripE :: Monad m => Enumeratee [ByteString] [ByteString] m a
rstripE = mapChunks (map rstrip)
Tenga en cuenta el map
adicional en rstripE
. El flujo externo (que es la entrada a rstrip) tiene el tipo [ByteString]
, por lo que necesitamos mapear rstrip
en él.
A modo de comparación, este es el aspecto que tendría si se implementara con convStream:
rstripE'' :: Enumeratee [ByteString] [ByteString] m a
rstripE'' = convStream $ do
mLine <- I.peek
maybe (return B.empty) (/line -> I.drop 1 >> return (rstrip line)) mLine
Esto es más largo y menos eficiente porque solo aplicará la función rstrip a una línea a la vez, aunque haya más líneas disponibles. Es posible trabajar en toda la porción disponible actualmente, que está más cerca de la versión de mapChunks
:
rstripE''2 :: Enumeratee [ByteString] [ByteString] m a
rstripE''2 = convStream (liftM (map rstrip) getChunk)
De todos modos, con el enumLinesBS
desmonte disponible, se compone fácilmente con el enumLinesBS
enumeratee:
enumStripLines :: Monad m => Enumeratee ByteString [ByteString] m a
enumStripLines = enumLinesBS ><> rstripE
El operador de composición ><>
sigue el mismo orden que el operador de flecha >>>
. enumLinesBS
divide el flujo en líneas, luego rstripE
. Ahora solo necesita agregar un consumidor (que es una iteración normal), y listo:
writer :: FilePath -> Iteratee [ByteString] IO ()
writer fp = I.mapM_ (B.appendFile fp)
processFile iFile oFile =
enumFile defaultBufSize iFile (joinI $ enumStripLines $ writer oFile) >>= run
Las funciones fileDriver
son accesos directos para simplemente enumerar sobre un archivo y ejecutar la iteración resultante (desafortunadamente, el orden de los argumentos se cambia de enumFile):
procFile2 iFile oFile = fileDriver (joinI $ enumStripLines $ writer oFile) iFile
Addendum: aquí hay una situación en la que necesitaría el poder adicional de convStream. Supongamos que desea concatenar cada 2 líneas en una. No puedes usar mapChunks
. Considere cuando el fragmento es un elemento singleton, [bytestring]
. mapChunks
no proporciona ninguna forma de acceder al siguiente fragmento, por lo que no hay nada más que concatenar con esto. Sin embargo, con convStream
es simple:
concatPairs = convStream $ do
line1 <- I.head
line2 <- I.head
return $ line1 `B.append` line2
esto se ve aún mejor en estilo aplicativo,
convStream $ B.append <$> I.head <*> I.head
Puede pensar que convStream
consume continuamente una parte del flujo con la iteración provista, y luego envía la versión transformada al consumidor interno. A veces, incluso esto no es lo suficientemente general, ya que se llama la misma iteración en cada paso. En ese caso, puede usar unfoldConvStream
para pasar el estado entre iteraciones sucesivas.
convStream
y unfoldConvStream
también permiten acciones monádicas, ya que la iteración de procesamiento de la secuencia es un transformador de mónada.
Estoy tratando de entender cómo usar la biblioteca iteratee con Haskell. Todos los artículos que he visto hasta ahora parecen centrarse en desarrollar una intuición sobre cómo se podrían construir las iteraciones, lo cual es útil, pero ahora que quiero bajar y usarlas, me siento un poco en el mar. Mirar el código fuente de iterate ha sido de valor limitado para mí.
Digamos que tengo esta función que recorta los espacios en blanco de una línea:
import Data.ByteString.Char8
rstrip :: ByteString -> ByteString
rstrip = fst . spanEnd isSpace
Lo que me gustaría hacer es: convertir esto en una iteración, leer un archivo y escribirlo en otro lugar con el espacio en blanco al final de cada línea. ¿Cómo voy a estructurar eso con iteraciones? Veo que hay una función enumLinesBS
en Data.Iteratee.Char en la que puedo ingresar, pero no sé si debería usar mapChunks
o convStream
o cómo volver a empaquetar la función anterior en una iteración.