cf. HaHaHa! – ハイフンで区切られた文字をキャピタライズ
cf. 趣味的にっき – ハイフンで区切られた文字をキャピタライズ
↑ここら辺を見て思い出したのが,Ruby on Rails (より正確には ActiveSupport)にある String#underscore。
String#underscore は大文字/小文字/数字からなる文字列を小文字/数字/アンダースコアからなる文字列に変換するメソッドで……つまりこんな感じ。
D:\>irb -rrubygems -ractive_support --simple-prompt
>> [ "Railes",
?> "ActiveSupport",
?> "Active1Support",
?> "Active1support",
?> "ToDoList",
?> "XML",
?> "XML2",
?> "XMLData",
?> "XMLdata",
?> "XML2Data",
?> "Iso2022jpMailer"
>> ].each do |s| p s.underscore end
"railes"
"active_support"
"active1_support"
"active1support"
"to_do_list"
"xml"
"xml2"
"xml_data"
"xm_ldata"
"xml2_data"
"iso2022jp_mailer"
=> ["Railes", "ActiveSupport", "Active1Support", "Active1support", "ToDoList", "
XML", "XML2", "XMLData", "XMLdata", "XML2Data", "Iso2022jpMailer"]
これを Haskell でやってみた。
‘_’ は大文字の直前に入るんだけど,前後の文字によって入ったり入らなかったりで,ちょっと複雑。なので State モナドを使ってみた。
import Control.Monad.State
import Data.Char
import System.Environment
data CharCase = UpperB4Upper | UpperB4Lower | UpperB4Number | UpperAtEnd | Lower | Number | None
cState :: Char -> CharCase
cState c | isUpper c = UpperB4Upper
| isLower c = Lower
| otherwise = Number
procChar :: Char -> State CharCase [Char]
procChar c = get >>= p
where
p UpperB4Upper = do put $ cState c
return [toLower c]
p UpperB4Lower = do put $ cState c
return $ "_" ++ [toLower c]
p UpperB4Number = do put $ cState c
if isUpper c then return [toLower c]
else return $ "_" ++ [c]
p UpperAtEnd = do put $ cState c
if isUpper c then return [toLower c]
else return $ "_" ++ [c]
p Lower | isUpper c = do put (UpperB4Lower)
return [toLower c]
| otherwise = do put $ cState c
return [c]
p Number | isUpper c = do put (UpperB4Number)
return [toLower c]
| otherwise = do put $ cState c
return [c]
p None = do put $ if isUpper c then UpperAtEnd else cState c
return [toLower c]
underScoreR :: String -> State CharCase String
underScoreR s = do strs <- mapM procChar $ reverse s return $ reverse $ concat strs underScore :: String -> String
underScore s = evalState (underScoreR s) None
UpperAtEnd は UpperB4Number と扱いが同じだから無くてもいいかも。
チェック用関数。
samples = [ ( "Rails" , "rails" )
, ( "ActiveSupport" , "active_support" )
, ( "Active1Support" , "active1_support" )
, ( "Active1support" , "active1support" )
, ( "ToDoList" , "to_do_list" )
, ( "XML" , "xml" )
, ( "XML2" , "xml2" )
, ( "XMLData" , "xml_data" )
, ( "2XMLdata" , "xm_ldata" )
, ( "XML2Data" , "xml2_data" )
, ( "Iso2022jpMailer" , "iso2022jp_mailer" )
]
checkSamples = mapM_ (putStrLn . check) samples
check s = (show $ underScore org == uds) ++ " " ++ org ++ " => " ++ uds
where
org = fst s
uds = snd s
実際にやってみると
*Main> checkSamples
Loading package mtl-1.0 ... linking ... done.
True Rails => rails
True ActiveSupport => active_support
True Active1Support => active1_support
True Active1support => active1support
True ToDoList => to_do_list
True XML => xml
True XML2 => xml2
True XMLData => xml_data
True XMLdata => xm_ldata
True XML2Data => xml2_data
True Iso2022jpMailer => iso2022jp_mailer
OKみたい。