モールス練習機の和文対応

ハムフェアの情報が欲しくて、CQ誌 を買ってきた。数え切れないぐらいのクラブが出展するんだな。 こりゃもう、行く鹿。今から楽しみ。

昔のCQ誌は、広告満載で、通販の部品をいろいろ吟味したものだけど、今は寂しいね。やっぱり 自作しよって人は少なくなったのか? それとも、アマチュア無線の人口自身が減ってしまったのか?

通販のページを見ていたら、球(真空管)の広告が出ていた。6BA6,6AV6,6AQ5 よくお世話になった MT管だ。残念ながら、我がいとしの807はもう無いのか。これが無いと送信機作れないじゃやん。

メーカーの広告を見ると、ケンウッドや八重洲も健在のようだ。ディジタル表示当たり前で、HF から1.2Gまでをカバーするリグとは、恐れ入る。

圧巻は、ICOMのスペクトラム・アナライザーに検波機能と送信機能を組み込んだリグ。こんな リグが市販されてるとは。タイムマシンに乗って(浦島さんの亀でもいいけど)過去から やってきた人になっちまったなあ。

幾ら、「ハムは趣味の王様」と言っても、ちょいとやり過ぎでないかいと、思ってしまうぞ。 まあ、人それぞれだから、お好きにどうぞ、と言うしか無いか。

Windows版 Haskellの文字コーディング

文字コーディングがどうなっているか調べようと思って、DOS窓を立ち上げたんだけど 日本語が入力出来ない。うつろな記憶では、「半角/全角」で切り替えられたと思って いたんだけど。。 どうやっても、半角しか入らない。

先生に聞いたら、ALTキーと併用するんだよとの事だった。ついでに、jpなんてコマンドを 教えてもらったので、やってみたら、cp-932 なんて、誇らしげに言ってきたよ。もう cp-932は全て廃止して、Windows7からは、utf-8に統一してくれよ。今やらないと、やる 時期がなくなっちゃうぞ。(みんな、Google OSになっちゃって、M$は、新しいOSを 出すのを諦めるから。)

さてさて、Haskellにも喋ってもらおう(2)を見た、親切な読者の方から

「Haskellにも喋ってもらおう(2)」の中で haskell の挙
動について誤解しているようです。日本語の文字コード
の問題に加えて Windows のシェルの仕様との絡みでわか
りにくいことになっているのですが、解決法は簡単です。
 .emacs に以下を加えるとよいです。

(add-hook 'inferior-haskell-mode-hook
 '(lambda ()
   (set-buffer-process-coding-system 'utf-8 'shift_jis)))

これを見るとどうしてここで shift_jis が出てくるのか
疑問に思うでしょう。 その理由は「repl の文字列
リテラルは shift_jis と解釈する」という挙動になって
いるからです。

sakae さんは emacs から haskell の repl を利用して
いますが、 Windows 上では一般に「コマンドプロンプト」
から利用するであろうという想定でこのような挙動になっ
ているものと思われます。

ファイルから読んだコードの文字列リテラルは UTF-8 と
して解釈されるので、一貫性に欠ける仕様ではあります
が、文字コード周辺は泥臭いことばかりなのでこれはこ
ういう仕様だと思うしかないですね。

と、いうわけで

 U.putStrLn $ tr "abc くつ"

を評価した結果がおかしくなるのは emacs と haskell
のやりとりの問題であって haskell のバグではありません。

実際に、上記を適用後、実験してみると

*Main> "くつ"
"\12367\12388"
*Main> putStrLn "くつ"
Od
*Main> U.putStrLn "くつ"
Loading package syb ... linking ... done.
Loading package base-3.0.3.1 ... linking ... done.
Loading package bytestring-0.9.1.4 ... linking ... done.
Loading package utf8-string-0.3.5 ... linking ... done.
くつ

ぱちぱちぱち。ちなみに、set-buffer-process-coding-system を知らなかったので、emacsに 教えを請うと

(set-buffer-process-coding-system decoding encoding)

Set coding systems for the process associated with the current buffer.
decoding is the coding system to be used to decode input from the process,
encoding is the coding system to be used to encode output to the process.

手を叩いて、喜んでいる場合ではないぞ。振り出しにもどっちゃった。 折角良い事を教えて頂いたので、「Haskellで喋る(3)」をやりたいのですが、ハムフェアも 迫っているので、こちらを先にやります。(どうしても、飛び入り出展したい、かな?)

モールス練習機の和文対応(その前に)

前回のencode結果を見ると、

ab cd efgh ij
slclssscWclslsclsscWcscsslscllscsssscWcssllsscssllssc

ちと醜い。CQ誌のCWを覚えるこつを読むと、"ooo---ooo" のように、記号で覚えちゃだめ、 "トトト ツーツーツー トトト"のように、音の感覚で覚えなさいって事が書いてある。だから、視覚 は、どうでもいいんだけど、ちょっと美意識に欠ける表示だった。

本来は、2チェンネラーのAAアート職人にでも、デザインしてもらえばいいんだろうけど、多少 見やすいようにしておこう。

ab cdc bb x
.-'-...'!'-.-.'-..'-.-.'!'-...'-...'!'..--..'

余計に醜くなったとか、rubyで作るなんとか から、ぱくって来たんだろうと言わないでね。 これでも、本人は一生懸命にデザインした(つもり)なんですから。 本当は、ワード区切りとして、'|' を使いたかったんだけど、cmd.exe が文句を垂れるので 断念しました。

予備実験として、前回のコードのモールステーブルに、('い', ".-") を追加してみたけど、 帰ってくるモールスコードは、"..--..'" (?)という結果。曰く、テーブルに 'い' な んてコードは存在しませんとなった。文字化けならぬ、モールスコード化けです。 やっぱり、Haskellで日本語難しいね。 でも、読者さんからのヒントで、方向は見えている。

Chaton haskell-jp に、助っ人現る

以前、このチャネルで、私のアホなコードを晒した時(Haskellでも喋ろう編)、もうちょっと何とかしたらと言って コードを示してくださった方がいた。 その時の生々しいログはこれ とか、こちら (その節はありがとうございました > nwnさん)

nwnさんのコード(外部のiconvを呼び出す版)を、モジュール化して、利用させて頂こう。

モジュール化と組み込み

彼のコードの利用例として

main = do
  let estdin = encodedHandle "CP932" IO.stdin
      estdout = encodedHandle "CP932" IO.stdout
  hPutStrLn estdout =<< hGetLine estdin
  main

のが、書かれている。私が当面必要になるのは、入力部分関数、hGetLine と、その引数で 参照されている、encodedHandle だ。この二つだけをモジュールから公開してもらえばいいのかな。

そんな方針で、ファイルの冒頭に

module Eh (encodedHandle, hGetLine) where

を、追加した。この追加によって、ファイル名は自動的に決まってくる。勿論、eh.hs と しましたよ。

さて、この eh.hs を、私が開発してる、morse.hs と同じDirに入れてから、私のソース(morse.hs)に モジュールのインポート宣言 import Eh を追加。

この状態で、emacsから morse.hs をロードすると、Eh なんてモジュール無いよと言われた。 はて? 自作(他作)の、ローカルモジュールは、どうやって認識させたらいいの? 並み居るhaskellサイトを見渡してみたけど、こういった事に言及してるサイトは、見つからなかった。 (悪い言い方をすれば、みんなメーカーの既製品しか使っていない。)

もう、自分で実験してみる鹿。きっと、モジュールはソースじゃだめなんだろう。だったら、 自作モジュールだけを、先にコンパイルしておこう。

c:\sakae\mo>ghc --make eh.hs
[1 of 1] Compiling Eh               ( eh.hs, eh.o )

こうしておいてから、emacs上で morse.hs を、ローディングしてみると

GHCi, version 6.10.3: http://www.haskell.org/ghc/  :? for help
Loading package ghc-prim ... linking ... done.
Loading package integer ... linking ... done.
Loading package base ... linking ... done.
Prelude> :load "c:/sakae/mo/morse.hs"
Ok, modules loaded: Eh, Main.
Prelude Main> 

無事にロード出来た。(自作品のロードは、emacsが気を利かせてくれるのかな?) 後は、emacs上で :main したいんですが、Eh内で使われている関数の制限により Exception が発生するので、実際の実行は、morse.hs を、コンパイルしておいて DOS窓から行います。

c:\sakae\mo>ghc --make morse.hs
[2 of 2] Compiling Main             ( morse.hs, morse.o )
Linking morse.exe ...
c:\sakae\mo>morse.exe
abc de
.-'-...'-.-.'!'-..'.'
abいろ deはに
.-'-...'.-'.-.-'!'-..'.'-...'-.-.'

最後に、eh.hs と morse.hs を掲載しておきます。(nwnさんに多謝!!)

module Eh (encodedHandle, hGetLine) where

import qualified Data.ByteString as S
import qualified Data.ByteString.Lazy as L
import qualified System.IO as IO
import Codec.Binary.UTF8.String as U
import System.Process

l2s :: L.ByteString -> S.ByteString
s2l :: S.ByteString -> L.ByteString

l2s = S.pack . L.unpack
s2l = L.pack . S.unpack

data EncodedHandle = EH { encoding_name  :: EncodingName
                        , wrapped_handle :: IO.Handle }

type EncodingName = String

iconv :: EncodingName -> EncodingName -> L.ByteString -> IO L.ByteString
iconv from to bs = do
  (inh, outh, errh, ph) <- runInteractiveProcess
                             "iconv" ["-f", from, "-t", to]
                             Nothing Nothing
--  mapM_ (flip IO.hSetBuffering IO.NoBuffering) [inh,outh,errh]

  L.hPut inh bs >> IO.hClose inh
  ret <- L.hGetContents outh >>= \c -> L.length c `seq` return c

  IO.hClose errh
  waitForProcess ph

  return ret

convertTo :: EncodingName -> L.ByteString -> IO L.ByteString
convertTo = iconv internalEncoding

convertFrom :: EncodingName -> L.ByteString -> IO L.ByteString
convertFrom = flip iconv internalEncoding

internalEncoding :: EncodingName
internalEncoding = "UTF-8"

hGetLine :: EncodedHandle -> IO String
hGetLine eh = do
  strictLine <- S.hGetLine . wrapped_handle $ eh
  decodedLazyBS <- convertFrom (encoding_name eh) (s2l strictLine)
  return . U.decode . L.unpack $ decodedLazyBS

hPutStrLn :: EncodedHandle -> String -> IO ()
hPutStrLn eh str = do
  encodedLazyBS <- convertTo (encoding_name eh) (L.pack . U.encode $ str)
  S.hPutStrLn (wrapped_handle eh) (l2s encodedLazyBS)
  IO.hFlush . wrapped_handle $ eh

encodedHandle :: EncodingName -> IO.Handle -> EncodedHandle
encodedHandle = EH

main :: IO ()
main = do
  let estdin = encodedHandle "CP932" IO.stdin
      estdout = encodedHandle "CP932" IO.stdout
  hPutStrLn estdout =<< hGetLine estdin
  main

a2mの中に独自で書いていた、モールスコードはテーブルの方に移してしまった。テーブルには 全部のコードを登録してないけど、今後は、テーブルのメンテナンスだけで、欧文、和文に 対応できます。

-- -*- utf-8 -*-
-- Morse sound generator (use external application iconv.exe mosc.exe)
-- Usage: morse.exe (input any char's and RET, then echo morse sound)

import System
import System.Process
import qualified System.IO as IO
import Data.Char
import Eh

type Table = [(Char,String)]
morse :: Table
morse = [('a', ".-"), ('b', "-..."), ('c', "-.-."), ('d', "-..")
         ,('e', "."), ('f', "..-."), ('g', "--."),  ('h', "....")
         ,('い', ".-"), ('ろ', ".-.-"), ('は', "-..."), ('に', "-.-.")
         ,('0',"-----"),('1',".----"), ('2',"..---"),  ('3', "...--")
         ,('(',"-.--.-"),(')',".-..-."),('@',".--.-."),('/', "-..-.")
         ,('\'',".----."), ('=',"-...-"), ('-',"-....-"), ('.',".-.-.-")
         ,(' ', "!"), ('?', "..--..")] -- don't delete this line

a2m :: Char -> String
a2m c  = case lookup (toLower c) morse of
            Just p -> p ++ "'"
            Nothing -> a2m '?'

encode  :: String -> String
encode s = concat $ map a2m s

mosc :: String -> IO ExitCode          -- allow only ".-'!" char's
mosc s = do { putStrLn s; rawSystem "mosc" [s] }

main :: IO ()
main = do
  let estdin = encodedHandle "CP932" IO.stdin
  mosc . encode =<< hGetLine estdin
  main