Haskell
文字列
package

Haskell の (文字列) 変換パッケージ (convertible, convert, conversion)

More than 1 year has passed since last update.

Haskell の文字列変換は非常に面倒です。全て自分で作っていればあまり表面化しない問題ではありますが、ライブラリ同士を組み合わせる時に String, ByteString, Text, Text.Lazy など、バラバラの文字列が利用されていることは少なく有りません。

本記事では、そのような場合の相互変換をどうすれば良いのか?という疑問を解決できると思います。

以下の記事に文字列変換について詳しく載っています。(記事の本文を読む前に、コメントを確認すると良いかもしれません)

さすがにこれを全部を覚えて使いこなすのはつらいので、そういう変換を行うパッケージを調べました。その結果、以下の3つが個人的に良さそうなのでご紹介します。

今回は文字列にのみ焦点を当てていますが、a -> b への変換という文字列に限定しない設計となっているため、幅広く利用できます。(例えば、文字列だけでなく「時間の変換」なども同じ仕組みを使うことができます)

結論から言えば convertible がオススメです。
文字列変換のみで考えると convert も良いのですが以下の点が気になります。

  • lts に入っていない (安定してない?)
  • 文字列以外の変換が convertible に比べて少ない

conversionByteString -> Text の変換が出来ないため、やめた方が良いです。(シンプルなものが好きな人は良いかもしれません)

基本的な使い方

3つとも convert 関数で変換を行います。以下は使い方の一例です。

>>> :set -XOverloadedStrings
>>> convert ("ByteString to Text" :: ByteString) :: Text
"ByteString to Text"
>>> :t convert ("ByteString to Text" :: ByteString) :: Text
convert ("ByteString to Text" :: BS.ByteString) :: Text :: Text

文字列変換対応表

型名は以下のように省略して表記します。

正式名称
String String
ByteString ByteString
ByteString.Lazy BL
Text Text
Text.Lazy TL
ByteString.Builder BB
ByteString.Lazy.Builder BLB
Text.Lazy.Builder TLB

String から別の文字列への変換には IsString クラスの fromString メソッドがあるのでそれを使えば良いでしょう。

  String ByteString BB BL BLB Text TL TLB
String fromString fromString fromString fromString fromString fromString fromString fromString

また、以下のパッケージ全てで ByteString -> String への変換 (Strict, Lazy 両方とも) は出来ませんでした。

The instances do not include conversions between ByteString and Text or String, since such conversions cannot safely be performed without knowing the encoding of the ByteString.

convertible パッケージの Data.Convertible.Instances.Text に書いてある説明文によると、ByteString のエンコーディングがわからないと安全に変換できないぞ!という理由です。

convertible

  String ByteString BL Text TL BB BLB TLB
ByteString X X convert convert convert convert convert convert
ByteString.Lazy X convert X convert convert convert convert convert
Text convert convert convert X convert convert convert convert
Text.Lazy convert convert convert convert X convert convert convert

convert

  String ByteString BL Text TL BB BLB TLB
ByteString X convert convert convert convert convert convert convert
ByteString.Lazy X convert X convert convert convert convert convert
Text convert convert convert convert convert convert convert convert
Text.Lazy convert convert convert convert convert convert convert convert
  • 型安全に利用することができるそうです。
  • tryConvert 関数があります。
  • 今後に期待できそうなパッケージだと思います。

conversion

  String ByteString BL Text TL BB BLB TLB
ByteString X convert convert X X convert convert X
ByteString.Lazy X convert convert X X convert convert X
Text convert convert convert convert convert convert convert convert
Text.Lazy convert convert convert convert convert convert convert convert
  • ByteString -> Text, ByteString -> Text.Lazy.Builder への変換はできません。

インストール方法

lts-9.3 を想定しています。

convertible パッケージ

lts に含まれているため、何も考えずに使えます。

convert パッケージ

lts に含まれていないため Hackage に登録されている convert-1.0.2 を使いたいのですが、ビルドできなかったので git から最新版を指定します。

-- stack.yaml
packages:
  - '.'
  - location:
      git: git@github:wdanilo/convert
      commit: 87082111ad27145687725f45e655a39af057dcaa
    extra-dep: true

conversion パッケージ

lts に含まれていないため Hackage の最新バージョンを extra-deps に指定します。

-- stack.yaml
extra-deps:
- conversion-1.2.1
- conversion-text-1.0.1
- conversion-bytestring-1.0.1

検証に使ったコード

convertible のみ掲載しておきます。残りは github にあります。

-- ConvertibleEx.hs
{-# LANGUAGE OverloadedStrings #-}
module ConvertibleEx where

import Data.Convertible (convert)
import Data.Convertible.Instances.Text ()

-- bytestring
import qualified Data.ByteString as B (ByteString)
import qualified Data.ByteString.Builder as BB (Builder)
import qualified Data.ByteString.Lazy as BL (ByteString)
import qualified Data.ByteString.Lazy.Builder as BLB (Builder)

-- text
import qualified Data.Text as T (Text)
import qualified Data.Text.Lazy as TL (Text)
import qualified Data.Text.Lazy.Builder as TLB (Builder)

-- utils
import System.IO (stdout)
import qualified Data.ByteString.Char8 as BC (putStrLn)
import qualified Data.ByteString.Builder as BB (hPutBuilder)
import qualified Data.ByteString.Lazy.Char8 as BLC (putStrLn)
import qualified Data.ByteString.Lazy.Builder as BLB (hPutBuilder)
import qualified Data.Text.IO as TIO (putStrLn)
import qualified Data.Text.Lazy.IO as TLIO (putStrLn)

main :: IO ()
main = do
  putStrLn "========  ByteString ========"
  -- putStrLn $ convert ("ByteString to String" :: B.ByteString)

  -- BC.putStrLn $ convert ("ByteString to ByteString" :: B.ByteString)
  BLC.putStrLn $ convert ("ByteString to ByteString.Lazy" :: B.ByteString)

  TIO.putStrLn $ convert ("ByteString to Text" :: B.ByteString)
  TLIO.putStrLn $ convert ("ByteString to Text.Lazy" :: B.ByteString)

  BB.hPutBuilder stdout $ convert ("ByteString to ByteString.Builder\n" :: B.ByteString)
  BLB.hPutBuilder stdout $ convert ("ByteString to ByteString.Lazy.Builder\n" :: B.ByteString)
  print $ (convert ("ByteString to Text.Lazy.Builder" :: B.ByteString) :: TLB.Builder)
  putStrLn ""

  putStrLn "========  ByteString.Lazy ========"
  -- putStrLn $ convert ("ByteString.Lazy to String" :: BL.ByteString)

  BC.putStrLn $ convert ("ByteString.Lazy to ByteString" :: BL.ByteString)
  -- BLC.putStrLn $ convert ("ByteString.Lazy to ByteString.Lazy" :: BL.ByteString)

  TIO.putStrLn $ convert ("ByteString.Lazy to Text" :: BL.ByteString)
  TLIO.putStrLn $ convert ("ByteString.Lazy to Text.Lazy" :: BL.ByteString)

  BB.hPutBuilder stdout $ convert ("ByteString.Lazy to ByteString.Builder\n" :: BL.ByteString)
  BLB.hPutBuilder stdout $ convert ("ByteString.Lazy to ByteString.Lazy.Builder\n" :: BL.ByteString)
  print $ (convert ("ByteString.Lazy to Text.Lazy.Builder" :: BL.ByteString) :: TLB.Builder)
  putStrLn ""

  putStrLn "========  Text ========"
  putStrLn $ convert ("Text to String" :: T.Text)

  BC.putStrLn $ convert ("Text to ByteString" :: T.Text)
  BLC.putStrLn $ convert ("Text to ByteString.Lazy" :: T.Text)

  -- TIO.putStrLn $ convert ("Text to Text" :: T.Text)
  TLIO.putStrLn $ convert ("Text to Text.Lazy" :: T.Text)

  BB.hPutBuilder stdout $ convert ("Text to ByteString.Builder\n" :: T.Text)
  BLB.hPutBuilder stdout $ convert ("Text to ByteString.Lazy.Builder\n" :: T.Text)
  print $ (convert ("Text to Text.Lazy.Builder" :: T.Text) :: TLB.Builder)
  putStrLn ""

  putStrLn "========  Text.Lazy ========"
  putStrLn $ convert ("Text.Lazy to String" :: TL.Text)

  BC.putStrLn $ convert ("Text.Lazy to ByteString" :: TL.Text)
  BLC.putStrLn $ convert ("Text.Lazy to ByteString.Lazy" :: TL.Text)

  TIO.putStrLn $ convert ("Text.Lazy to Text" :: TL.Text)
  -- TLIO.putStrLn $ convert ("Text.Lazy to Text.Lazy" :: TL.Text)

  BB.hPutBuilder stdout $ convert ("Text.Lazy to ByteString.Builder\n" :: TL.Text)
  BLB.hPutBuilder stdout $ convert ("Text.Lazy to ByteString.Lazy.Builder\n" :: TL.Text)
  print $ (convert ("Text.Lazy to Text.Lazy.Builder" :: TL.Text) :: TLB.Builder)
  putStrLn ""

まとめ

  • convertible パッケージを使っておけば良い。
  • 現状、ByteString -> String への変換は提供されていない。
  • 文字列以外の変換も提供されている。
  • 少し改良したらInt -> Int# みたいな変換も実装できそう。

参考