WinHugs

アケオメ、コトヨロ

unix系は色々なhaskell系が有るんで、好きに入れればいいけど、Windows系はどうする? ghcをいれようとすると、色々なものを引き連れてきて、大掛かり過ぎる。

ならば、WinHugsだな。 まずは最小の方でもと思って取って来てインストールしようとしたら、途中でコケて インストール出来ない。Hugs98.chmが、Could Not ... とか言って展開しないんだ。 結果、インストール失敗と相成る。 もう一つの大きい方も同様にコケる。古いソフトですからコケが 生えたんですな。

違うって、オイラーがいつまでたってもWindows7からWindows10に アップデートしないものだから、M$が嫌がらせしてるんですよ。早く10にしろ10にしろと 起動のたびに出てきて、M$ウィルスに感染しましたな。chmなんていう恥ずかしい遺産を 早く亡きものにしたいのです。

どうしてくれよう? exeファイルって、zipファイルに自己解凍のunzipを合体させた ものだろうと野生の感が働いた。zipを展開するソフトにかけたら、中身が出てきた。 dir毎、適当な所に配置して、winhugsのアプリのリンクを作って、設置完了。

正規のインストールじゃなくて、野良インストールなんで、ファイルの関連付けなんて勿論出来て いない。試運転を兼ねて、life.lhsを起動しましょ。そうすると、このファイルは、どの アプリから起動しますかって聞いてくるんで、Winhugsを指定。通常よく使われてhoge.hs とかもWinHugsに関連付けておこう。

試運転は、life.lhsだな。このアプリを解説してる『プログラミングHaskell』(オーム社)本は、 非常に良い本、何度でも読み返したくなるSICPみたいな本だぞ。

走らせてみると、グライダーが滑降しないで、

[1;320HO[2;320HO[3;220HO[2;120HO[3;320HO{Interrupted!}

(39823879 reductions, 51863040 cells, 57 garbage collections)
Main> 

こんなのが出てきたんで、止めるボタンを押して止めた。WinHugsのコンソール画面って エスケープシーケンスが効かない、Windowsな世界なのね。

悪あがきのついでに、コマンド.コムな端末窓からhugsを起動してlife.lhsが動くか確認。 やっぱりエスケープシーケンスを理解出来なくて、そのまま文字表示しちゃう。

端末窓は他に無いのか?ってゲイツ閣下に聞いてみたら、DOS由来の窓から少し進化した 体系が有ると言う。もったいぶっていないで早く教えろ。

それはね、Power Shellの窓でございます。今お使いのWindows7 Home-エディションにも 搭載してございますから、アクセサリーの所を見てください。

シェルと言う割りには起動が遅いな。で、試してみるとやはりエスケープシーケンスは 理解出来ない窓端末でした。そのわりには、emacs -nw で、窓にemacsを貼り付けると ちゃんと動くなあ。emacs君は大分無理して、Windowsに合わせているんでしょう。 ご同情申し上げます。

頑張ってWindowsでもコマンドしたい!ConEmuとMSYS2を入れてみよう をやるか。cmd.exe基礎 にも紹介が有るなあ。

試しにConEmuを入れてみた。ちゃんとエスケープシーケンスを理解し、グライダーが滑降 してくれた。こうでなくちゃね。

ああ、ConEmuの設定とか扱い方は、 Windows標準のコマンドプロンプトウィンドウをタブ化できる「ConEmu」 に出てた。WindowsでUnixもどきを体験したい時は必須のソフトだな。

最近はmsysもmsys2に進化して、pacmanを使ってるんか。MSYS2 installer

Win32

でWinの流儀でGUIかと気付いたオイラーは、Windowsユーザーだけの特典があるに違いないと 思って、提供物を家宅捜査してみた。demos\Win32\hello.lhs を発見、補足しました。

こいつを読みやすくする為に、早速emacsにhaskell-modeを突っ込んであげましたよ。そして、

; hugs
(setq haskell-program-name "hugs")
(add-hook 'haskell-mode-hook 'turn-on-haskell-indent)
(autoload 'haskell-mode "haskell-mode")
(add-to-list 'auto-mode-alist '("\\.hs$" . haskell-mode))
(add-hook 'haskell-mode-hook 'inf-haskell-mode)

こんなemacsの設定を書いたら、haskell-modeからhugsを呼び出せたよ。

ああ、WinHugsを起動した時にロードされるhsなファイルを表示するように設定してる。 それがリンクになってるんで、クリックするとemacsが立ち上がって閲覧出来る。 さすがGUIの世界だな。

module Main(main) where

import qualified Graphics.Win32
import qualified System.Win32.DLL
import qualified System.Win32.Types
import Control.Exception (bracket)
import Foreign
import System.Exit

main :: IO ()
main =
  Graphics.Win32.allocaPAINTSTRUCT $ \ lpps -> do
  hwnd <- createWindow 200 200 (wndProc lpps onPaint)
  messagePump hwnd

onPaint :: Graphics.Win32.RECT -> Graphics.Win32.HDC -> IO ()
onPaint (_,_,w,h) hdc = do
   Graphics.Win32.setBkMode hdc Graphics.Win32.tRANSPARENT
   Graphics.Win32.setTextColor hdc (Graphics.Win32.rgb 255 255 0)
   let y | h==10     = 0
         | otherwise = ((h-10) `div` 2)
       x | w==50     = 0
         | otherwise = (w-50) `div` 2
   Graphics.Win32.textOut hdc x y "Hello, world"
   return ()
{- 以下、ぐちゃぐちゃとコードが続く -}

ってなのが載ってて、走らせてみると、ハロワ画面が出てきましたよ。

HGL

もっと簡単なの、お隣にあった。HGLとかいうハロワ。

module Main(main) where

import Graphics.HGL

main :: IO ()
main = runGraphics $ do
  w <- openWindow "Hello World Window" (300, 300)
  drawInWindow w (text (100, 100) "Hello")
  drawInWindow w (text (100, 200) "World")
  getKey w
  closeWindow w

これだけで、GUIな窓が出てくる。何かキー入力すると窓が引っ込む。なんかTkっぽいな。 実用にするには、裏で色々ロードしてるWin32のモジュールとかHGLのモジュールを 見ておかないといけないね。Test.hsとかGTest.hsは多少参考になるけど、他に 何処かに資料を置いていないかな?

ぐぐってみたら、自分が昔やったのが出てきた。 もう6年も前。時代は巡るなあ。

走らせてみると

ERROR file:.\ball.hs:54 - Type error in value construction
*** Expression     : BallObj {dx = speed0 * 4, dy = speed0, x = 0, y = 0}
*** Term           : speed0 * 4
*** Type           : Double
*** Does not match : Float

こんな風に文句を言ってきたんで、Doubleに全部変更。そしたら、あっさり動いた。 昔は、Windowsにghcの組み合わせで悪戦苦闘してたってのに、どゆ事。hugsの方が出来が いいじゃん。

ghc系でも動くはずってんで、HGL package になってるのはいいんだけど、使い方の説明って無いの? 自慢の

Main> :m Graphics.HGL
Graphics.HGL> :browse
module Graphics.HGL where
runGraphics :: IO () -> IO ()
Unbuffered :: RedrawMode  -- data constructor
  :
par :: IO a -> IO b -> IO (a,b)
par_ :: IO a -> IO b -> IO ()
parMany :: [IO ()] -> IO ()

これだけで、コードを書けってのは、大いに無理が有りますよ。 The hgl-example packageでも 読んでくださいって事かな。

いずれにしてもGUIは労多くして得る所無しって代物だから、余り近寄らない方がいいぞ。

hugs basic

去年書いた、まともなhaskellアプリがgoferで動かないか検討中。何ってたって、hugsはgoferを 元に発展させませたって堂々とWinHugsのFAQに書いてありましたから。。。

現代のものが、昔の流儀で動くかプチ興味が有ったのさ。で、preludeを眼grepした限りでは、 文字列な数字を整数に変換する関数が見つからなかった。そのあたりを補う方法を 考えておこう。

C語で言うと、atoiって関数。どうやって実現してる?ちょいとOpenBSDにスパイを放って みる。(NetBSDのそれは、ちょっとぐちゃぐちゃしてて、追うのは大変)

/*  /usr/src/lib/libc/stdlib/atoi.c      */
int atoi(const char *str) {
        return((int)strtol(str, (char **)NULL, 10));
}
/*   /usr/src/lib/libc/stdlib/atrtol.c    */
long strtol(const char *nptr, char **endptr, int base) {
                 :
        for (acc = 0, any = 0;; c = (unsigned char) *s++) {
                if (isdigit(c))
                        c -= '0';
                 :
                                acc *= base;
                                acc += c;
                  :
        return (acc);
}

ふむ、atoiってのはフロント役をやってて、バックにはstrtolとかstrtollが控えて いるのね。で、バックの仕事ぶりは、結構精緻になってた。本質が欲しいので、しっぽから 見てくと、accが答えとな。遡ってみると、acc = acc * 10 + c ってのが骨格だ。 計算結果を累積してんのね。

方法が分かれば、後はそれをHaskell流に移植するだけ。

-- Like atoi strtol
import Data.Char

d2i :: Char -> Int
d2i c = ord c - ord '0'

ri :: String -> Int
ri s         = ri' s 0
ri' "" _     = 0
ri' (s:"") n = n * 10 + d2i s
ri' (s:ss) n = ri' ss (n * 10 + d2i s)

hugsでやる時は、ordがData.Charに移ってしまったので、importしておかないと悲しい事に なる。riがatoiに相当して、riダッシュってのがstrtolに相当するかな。d2iは一桁の文字を 数値に変換するやつだ。空文字の場合は未定義でもよかったんだけど、慣習でゼロを 返している。

そんじゃ、hugsでは、どう実現してるか、見ておく。Numericモジュールの素性を調査。 hugsの場合、コロンmコマンドは、現モジュールを切り替える機能しかないので、あらかじめ ロードしておく事。ghciは、コロンmコマンドで、モジュールを取り込んでくれる親切設計に なってた。

after import Numeric
:m Numeric
Numeric> :browse
module Numeric where
showSigned :: Real a => (a -> ShowS) -> Int -> a -> ShowS
showIntAtBase :: Integral a => a -> (Int -> Char) -> a -> ShowS
showInt :: Integral a => a -> ShowS
showHex :: Integral a => a -> ShowS
showOct :: Integral a => a -> ShowS
showEFloat :: RealFloat a => Maybe Int -> a -> ShowS
showFFloat :: RealFloat a => Maybe Int -> a -> ShowS
showGFloat :: RealFloat a => Maybe Int -> a -> ShowS
showFloat :: RealFloat a => a -> ShowS
floatToDigits :: RealFloat a => Integer -> a -> ([Int],Int)
readSigned :: Real a => ReadS a -> ReadS a
readInt :: Integral a => a -> (Char -> Bool) -> (Char -> Int) -> ReadS a
readDec :: Integral a => ReadS a
readOct :: Integral a => ReadS a
readHex :: Integral a => ReadS a
readFloat :: RealFrac a => ReadS a
lexDigits :: ReadS String
fromRat :: RealFloat a => Rational -> a
Numeric> readHex "fffx3"
[(4095,"x3")]

最後に、ちょろっと、16進を数値に直してみた。失敗した所と成功した所と分けて 返ってくる仕様なのね。

readDec = readInt 10 isDigit    (\ d -> fromEnum d - fromEnum_0)

readInt :: Integral a => a -> (Char -> Bool) -> (Char -> Int) -> ReadS a
readInt radix isDig digToInt s =
    [(foldl1 (\n d -> n * radix + d) (map (fromIntegral . digToInt) ds), r)
        | (ds,r) <- nonnull isDig s ]

10進変換の定義。複雑な内包処理をしてるなあ。どういう動きをするのか追ってみる。 内包表記なんで、縦棒の左と右を考える。まずは右から。nonnullって何だ?

type ReadS a = String -> [(a,String)]

nonnull                 :: (Char -> Bool) -> ReadS String
nonnull p s             =  [(cs,t) | (cs@(_:_),t) <- [span p s]]

spanして出来るタプルをリストに押し込めたものかな。

Main> span isDigit "123x456"
("123","x456")
Main> nonnull isDigit "123x456"
[("123","x456")]

次は縦棒の左側。棒の右側の結果の正常な文字列を整数に変換し、異常文字を含むものと タプルに組み上げているんだな。

foldl1 (\n d -> n * radix + d) (map (fromIntegral . digToInt) ds)

mapを使って、数字文字列を、Intなリストに変換。

Main> map (fromIntegral . digToInt) "12345"
[1,2,3,4,5]

ここに出て来るdigToIntは、オイラーの定義した d2iの事ね。d2iで使ってるordってのは 文字コードに相当する整数を返す関数。昔はASCIIコードだけを対象にしてれば良かったけど、 世の中の動きにつれて、全世界共通文字コードを返すように拡張されてる。だからきっと、 漢数字でも動くに違いない。壱弐参。

Main> ord '壱'
ERROR - Improperly terminated character constant

Hugsってまだサポートが足りない? ghcだと

Prelude Data.Char> ord '壱'
22769
Prelude Data.Char> ord '弐'
24336

ああ、無駄な事をしたわい。話を戻して、得られた 整数リストに対してfoldl1を適用するんか。 foldl1ってのは

foldl1           :: (a -> a -> a) -> [a] -> a
foldl1 f (x:xs)   = foldl f x xs

foldl            :: (a -> b -> a) -> a -> [b] -> a
foldl f z []      = z
foldl f z (x:xs)  = foldl f (f z x) xs

リストの最左の値を初期値として、左畳み込みをするんだな。畳み込みは右からも出来る。 更に言語による流派もある。詳しくは、リストの畳み込みを参照

etc

去年のAdvent Calendarで興味を引いたのは、NetBSDの所にあったもの。

マジックナンバー0x10000004の怪

NetBSD/arm 6.xでgolangが動くまで

そしてHaskellグループでも。

入門 StateTモナド

Stateモナドで遊んでみよう

目指せワンライナー! ? 1行コードでがんばる

SMLも頑張っている。 SML#でJITコンパイラを作る軽い話

コンパイラ: JVMバイトコードへのコンパイル