yunomuのブログ

酒とゲームと上から目線

readを使って文字列をRead aに変換しようとする

最初はreadTextみたいな関数を作ろうとしていました。
readの、StringじゃなくてTextを取る版。

readText :: Read a => Text -> a

で、最初はこんな感じで実装しようとしていた。

module Main where

import Data.Text (Text)
import qualified Data.Text as T

readText :: Read a => Text -> a
readText = read . T.unpack

unpackしてできたStringをreadに食わせれば終わりじゃないか。
と思っていたんですが、
ghciで試してみると

Main> :set -XOverloadedStrings
Main> :m Data.Text
Main Data.Text> readText "193" :: Int
193
Main Data.Text> readText "abide" :: Text
"*** Exception: Prelude.read: no parse

TextをTextに変換しようとすると失敗する。
「TextってReadのインスタンスだよね? なんで?」って一人で大騒ぎしていました。

というかよくよく考えて試してみると、

Prelude> read "841" :: Int
841
Prelude> read "abc" :: String
"*** Exception: Prelude.read: no parse

Stringでもunpackとかしなくても普通に失敗する。Intはうまくいくのに。

ヒントはもう出ていて、`"*** Exception:`のところの最初のダブルクォーテーション、文字列をshowした時に出るやつで、文字列はshowしたりする時はダブルクォーテーションで囲まれる。
同様に、readに渡す文字列は文字列の文字列表現の形をとっている必要があって、
つまり

Prelude> read "\"abc\"" :: String
"abc"

文字列は文字列らしくダブルクォーテーションで囲めばよろしい。
文字列だと思ってparseしようとしたのにダブルクォーテーションが無かったから困ったねって事になっていたようです。

ということで、最初のreadTextの実装を変更してみる。unpackじゃなくてshowしてやればいいじゃないか。

readText :: Read a => Text -> a
readText = read . show

showしてreadってなんかなんじゃそりゃって感じですけどね。

でもこれはこれで問題があって、

Main> readText "186" :: Int
*** Exception: Prelude.read: no parse

今度は数字が読めなくなっている。全然駄目だ。
showしたことで文字列がダブルクォーテーションで囲まれてしまって、数字としてreadできなくなったということで。
そういう時だけは(read . unpack)でいいんだけど。

で、最終的にSafeモジュールのreadMayを使ってこうなった。

import Data.Text (Text)
import qualified Data.Text as T
import Safe (readMay)
import Data.Maybe (fromMaybe)

readText :: Read a => Text -> a
readText t = fromMaybe (read $ T.unpack t) . readMay . show $ t

文字列系に変換するパターンの方が圧倒的に多かったのでこういう感じに。文字列以外に変換するパターンが多かったら、showとunpackの位置が逆になるんじゃないでしょうか。

ということで出来上がったのはreadではなくて、なんか、汎用のデータ変換関数? 結局readとは少し違うものができてしまいましたし、最初から目指していたのはこれでした。

この変換関数を作成中のプロダクトの基盤部分に突っ込んでみた結果、それはそれで「型推論できないぞ」って怒られまくったんですが、それはまた別の話。
readを使いこなすのって難しいですよねぇ。というかあんまし使いたくないけども。