You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Based on discussion in #442, having a way to write a text builder directly to a handle, like bytestring's hPutBuilder might be a nice performance win in some situations.
I would like to have a function with which I can write a Text Builder to a Handle while specifying the encoding, e.g. hPutBuilderUtf8.
For example,
importSystem.IOimportqualifiedData.Text.Lazy.IOasTL
main = withBinaryFile "test.txt"WriteMode$\h ->TL.hPutStr h "Gebäude"
With text (at least version 1.*) this writes "garbage" into the file in the sense that it writes "modulo-truncated UTF-16", which cannot be read by most other programs.
For small values like the above one could call encodeUtf8 directly, but for large builder values one really wants to use an equivalent of TL.hPutStr which doesn't pick a non-useful encoding on withBinaryFile, and instead allows providing the encoding directly as an argument or in the function name.
TB.toLazyText + encodeUtf8Builder is unnecessary indirection. The text's Builder is filling up buffers, these can be output directly to the Handle without allocating anything else in between (reusing the same buffer!)
@phadej I do not see how this is any different using the lazy text hPutStr since Builder can only produce [StrictText]. It seems like this only cuts out the functions toLazyText and toChunks.
The Builder type in text is defined as a producer of a list of chunks: its only purpose is to produce a LazyText. The only possible implementation of hPutBuilder is the composition of Lazy.hPutStr and Builder.toLazyText.
A better data structure for outputting to the OS is bytestring's Builder: it doesn't allocate its own buffers, allowing us to reuse a single one to send an output. There are already UTF-8 conversions to bytestring Builder from both StrictText and LazyText.
That's admittedly not very discoverable. I'm not sure how to improve this.
@Lysxia You are correct with the current implementation of builder. However, in #586 I have modified Builder to be able to also be able to use pinned buffers. Pinned buffers allow the handle to use the same buffer that the builder uses thus skipping the copy from the builder buffer to the handle char buffer. This is helpful for all encodings that are not UTF-8.
I'm not convinced adding hPutBuilder is the right thing to do. If you want to append text values efficiently before printing them (which is where this issue originally comes from), you go through ByteString.Builder with encodeUtf8Builder. Text.Lazy.Builder is the wrong data structure for this (and it's specialized to UTF-8, whereas one could implement other encoders to ByteString.Builder), and since there is already a suitable data structure elsewhere, there's no point changing it.
Activity
nh2 commentedon Jul 4, 2023
I would like to have a function with which I can write a Text Builder to a
Handlewhile specifying the encoding, e.g.hPutBuilderUtf8.For example,
With
text(at least version 1.*) this writes "garbage" into the file in the sense that it writes "modulo-truncated UTF-16", which cannot be read by most other programs.For small values like the above one could call
encodeUtf8directly, but for large builder values one really wants to use an equivalent ofTL.hPutStrwhich doesn't pick a non-useful encoding onwithBinaryFile, and instead allows providing the encoding directly as an argument or in the function name.nh2 commentedon Jul 4, 2023
I suspect that
Data.Text.Lazy.Encoding'sencodeUtf8Builder :: Lazy.Text -> ByteString.Builderdoes the job:Efficient file write without forcing the entire
Textinto memory.{-# LANGUAGE OverloadedStrings #-} import Data.ByteString.Builder (hPutBuilder) import qualified Data.Text.Lazy.Builder as TB import Data.Text.Lazy.Encoding (encodeUtf8Builder) import System.IO main = do withBinaryFile "test.txt" WriteMode $ \h -> do hPutBuilder h (encodeUtf8Builder $ TB.toLazyText $ TB.fromText "Gebäude")phadej commentedon Jul 4, 2023
TB.toLazyText+encodeUtf8Builderis unnecessary indirection. Thetext'sBuilderis filling up buffers, these can be output directly to theHandlewithout allocating anything else in between (reusing the same buffer!)nh2 commentedon Jul 4, 2023
@phadej Agreed. It would be nice to have a function that goes straight from a text
Builderto syscalls.BebeSparkelSparkel commentedon Apr 25, 2024
@phadej I do not see how this is any different using the lazy text hPutStr since Builder can only produce
[StrictText]. It seems like this only cuts out the functionstoLazyTextandtoChunks.BebeSparkelSparkel commentedon Apr 25, 2024
It seems that for this to work well a few changes need to be made
ensureFreeshould use a given buffer size similar to toLasyTextWith so that it can match the handles buffer sizenewBuffershould be able to usenewornewPinnedbecause newPinned will allow the handle to use the buffer without copyingLysxia commentedon May 27, 2024
The
Buildertype in text is defined as a producer of a list of chunks: its only purpose is to produce aLazyText. The only possible implementation ofhPutBuilderis the composition ofLazy.hPutStrandBuilder.toLazyText.A better data structure for outputting to the OS is bytestring's
Builder: it doesn't allocate its own buffers, allowing us to reuse a single one to send an output. There are already UTF-8 conversions to bytestringBuilderfrom bothStrictTextandLazyText.That's admittedly not very discoverable. I'm not sure how to improve this.
BebeSparkelSparkel commentedon May 29, 2024
@Lysxia You are correct with the current implementation of builder. However, in #586 I have modified Builder to be able to also be able to use pinned buffers. Pinned buffers allow the handle to use the same buffer that the builder uses thus skipping the copy from the builder buffer to the handle char buffer. This is helpful for all encodings that are not UTF-8.
Lysxia commentedon May 29, 2024
I'm not convinced adding
hPutBuilderis the right thing to do. If you want to append text values efficiently before printing them (which is where this issue originally comes from), you go throughByteString.Builderwith encodeUtf8Builder.Text.Lazy.Builderis the wrong data structure for this (and it's specialized to UTF-8, whereas one could implement other encoders toByteString.Builder), and since there is already a suitable data structure elsewhere, there's no point changing it.BebeSparkelSparkel commentedon May 29, 2024
I see what you mean. Thanks
BebeSparkelSparkel commentedon May 29, 2024
Seems like this should be closed.