new ghc

Table of Contents

new ghc

ghcのソースを見つけてしまったものだから、試してみたい。

https://www.haskell.org/ghc/ ああ、これがGHC専用サイトだな。

haskellと言えばGHC、野球と言えば大谷のごとく、裏で得体の知れない大きな 力が作用してると思うんだけど、どうよ?

9.2.x, 9.4.x, 9.6.x の3本柱体制なのか。ならば先端を行ってみたい。多分、 茨の道だろうけど。

INSTALL.md

Quick start:  the following gives you a default build:

    $ ./boot
    $ ./configure
    $ ./hadrian/build

こんな工程が案内されてた。bootのフェーズで、必須のモジュールのソースが DLされて、既存のghcにより、configureが作成されるんだな。

OpenBSD(32bit)では、種になるghcが既に無いので、新しいghcは作成出来ない 状態になっている。

Configure completed successfully.

   Building GHC version  : 9.7.20230601

   Happy        : /home/sakae/.cabal/bin/happy (1.20.1.1) -- yacc
   Alex         : /home/sakae/.cabal/bin/alex (3.3.0.0)   -- lex or flex
   sphinx-build :
   xelatex      :
   makeinfo     : /usr/bin/makeinfo
   git          : /usr/bin/git
   cabal-install : /home/sakae/.ghcup/bin/cabal

----------------------------------------------------------------------

For a standard build of GHC (fully optimised with profiling), type (g)make.

To make changes to the default build configuration, copy the file
mk/build.mk.sample to mk/build.mk, and edit the settings in there.

For more information on how to configure your GHC build, see
   https://gitlab.haskell.org/ghc/ghc/wikis/building

configure時に、happyとalexは必須だ。追加で入れたよ。ドキュメント関係に はpythonも必要っぽいけど、とりあえず省略。そして、最後の長い工程へと進 んで行く。

| Run Ghc CompileHs Stage1: compiler/GHC/StgToCmm/Prim.hs => _build/stage1/compiler/build/GHC/StgToCmm/Prim.p_o
^C
real    219m5.335s        35m43.883s
user    200m18.025s  +    31m22.239s
sys     15m40.545s         1m37.070s

とんでもない時間がかかったので、途中一回休み。翌日、同じコマンドを叩い たら、継続してくれた。stage1が有るって事は、stage2も、やり方によっては 作成できるんだな。

-j8ぐらいで使えるマシンが欲しい。Windows7時代のi386な 石じゃ、何をするにも役不足。

/----------------------------------------------------------\
| Successfully built program 'ghc-bin' (Stage1).           |
| Executable: _build/stage1/bin/ghc                        |
| Program synopsis: The Glorious Glasgow Haskell Compiler. |
\----------------------------------------------------------/

sakae@deb:~/MINE/src/ghc$ cd _build/stage1/
sakae@deb:~/MINE/src/ghc/_build/stage1$ ls
bin/       ghc/      lib/     libraries/  share/
compiler/  inplace/  libffi/  rts/        utils/
sakae@deb:~/MINE/src/ghc/_build/stage1$ ls -sk bin/
total 10416
2172 ghc*      5944 haddock*   712 hpc*       64 runghc*
 612 ghc-pkg*    52 hp2ps*     860 hsc2hs*
sakae@deb:~/MINE/src/ghc/_build/stage1$ bin/ghc --version
The Glorious Glasgow Haskell Compilation System, version 9.7.20230601

軽く確認。

sakae@deb:~/MINE/src/ghc/_build/stage1$ bin/ghc-pkg list
/home/sakae/MINE/src/ghc/_build/stage1/lib/package.conf.d
    Cabal-3.10.1.0
    Cabal-syntax-3.10.1.0
    array-0.5.5.0
    base-4.18.0.0
    :
    ghci-9.7
    :

全体が4.8Gと言う巨大なものになった。モジュールは同梱してくれているけど、 肝心のスタンダローンで動くcabalが無い。どうやって作成するんだろう? これが無いと、何も出来ないな。juliaみたいに、パッケージャーと一体になっ た物が便利と思うぞ。

次は、 hadrian のあたりを見て突き進めばいいのか。なんか、古いパソコンに負担をかけるの は、気が引けるなあ。

Linux vs. *BSD

ほっとして翌日Linuxを起動したら、WiFiが死んでいた。じゃ、FreeBSDではど うかと試したら、やはりダメ。shutdown -r nowして、grubでFreeBSDに切り替 えちゃったんで、ハードのリセットが効かなかったかも。 きっと、前日の疲れが残っていたんでしょうと言うことで、諦め。

翌日リナを起動すると、やはりWiFiが死んでた。しょうがない、本腰を入れて 調べてみるか。dmesgだな。が、不親切で何も記録はなし。次は/var/log あたりか。面倒なので、FreeBSDに切り替え。dmsgしたら、wlan0なデバイスが disableになってるよと一言あった。

ああ、思い出した。ハードのSWがついてた事を。点検したらOFFになってた。 夜中にダエモン君が出てきて、余りパソコンを酷使しないで下さいと、警告し てったんだな。

それにしてもリナのdmsgにはハードに対する愛が無いと思うぞ。対して*BSDな 人は、ハードを愛してる。余りに愛すぎるばかりに、VAXなミニコンのボード をひっこぬき、パターン・カット、ICの足あげとかジャンパー線を飛ばす改造 も訳なくやっちゃう。こういう人の元で成長してきたBSDは、必要にして十分 な情報が提供されてるよ。

3種の神器、半田コテ、パターンカット用の電動グラインダー、スェーデン製 の刃先がモリブデン鋼で出来たニッパー。ええ、オイラーも現役時代に使って いましたとも。

debug

思い出したように、ghciのdebuggerで遊ぶ。

ghci> :break Eval.textToEvalForm
Breakpoint 0 activated at app/Eval.hs:107:28-93
ghci> :main -r
Repl> (+ 3 4)
Stopped in Eval.textToEvalForm, app/Eval.hs:107:28-93
_result :: LispVal.Eval LispVal.LispVal = _
input :: Data.Text.Internal.Text = _
std :: Data.Text.Internal.Text = _
[app/Eval.hs:107:28-93] ghci> :set stop :list

stepで止まった時にリストする様に指示

[app/Eval.hs:107:28-93] ghci> :step
Stopped in Eval.textToEvalForm, app/Eval.hs:107:28-68
_result ::
  Either Text.Parsec.Error.ParseError LispVal.LispVal
  -> LispVal.Eval LispVal.LispVal = _
106  textToEvalForm :: T.Text -> T.Text -> Eval LispVal
107  textToEvalForm std input = either (throw . PError . show )  evalBody $ parseWithLib std input
                                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
108

ちょっと鬱陶しいけど、何処を評価するか表示してくれるようになった。

[app/Eval.hs:96:13-35] ghci> show std
"\"(define caar (lambda (pair) .... (even (- x 1)))))\\n\\n\\n\""

そして、何でも見せますが便利だ。

Haskellでのデバッグ手法 こういう楽しいのが有るなあ。

第15回 Haskellでのデバッグのコツをつかむ 大事な話だ!!

Haskell でのデバッグ 山本先生降臨

Debug.trace

お勧めとの事なので、やってみる。ソースを変更して実行するので、ghc –make の、PDCAが速く回せる環境が良い。

指定した場所を通過したか、変数の値はどうか、確認出来る。

Eval.hs

import Debug.Trace

textToEvalForm :: T.Text -> T.Text -> Eval LispVal
textToEvalForm std input = either
  (throw . PError . show )
  evalBody $ parseWithLib std $ trace ("in Form" ++ show std) input

元は、 parseWithLib std input となっていた場所に割り込み。通過した印に文字列と引 数を表示。あとは何事も無かったように振る舞う。$ を忘れるな。

sakae@deb:/tmp/cs/app$ ./a.out -r
Repl> (+ 3 4)
inForm "(define caar (lambda (pair) ...  #f (even (- x 1)))))\n\n\n"
7

確かに、通過しましたねぇってのが判る。割り込む場所の選定に、頭を悩ます な。

Exception

使っているんだよなあ。ちゃんとよんでおく。

Haskell 例外処理 超入門

実際は、throwが、下記の2例の方法で利用されている。

parseFn val = throw $ TypeMismatch "parse expects string, instead got: " val

lineToEvalForm input = either (throw . PError . show  )  eval $ readExpr input

投げる例外は型が定義されてる。下記が例の一部。

data LispException
  = NumArgs Integer [LispVal]
  | TypeMismatch T.Text LispVal
  | PError String -- from show anyway
  :

instance Exception LispException
instance Show LispException where
  show = T.unpack . showError

例外の実例はリスプの例外ですよ、リスプ例外の見せ方は、こうだからねって 宣言。そして、そのエラーの具体的な表示って訳だ

showError :: LispException -> T.Text
showError err =
  case err of
    (TypeMismatch txt val)   -> T.concat ["Error Type Mismatch: ", txt, showVal val]
    (PError str)             -> T.concat ["Parser Error, expression cannot evaluate: ",T.pack str]
    :

repl

schemeとかはreplが使用される。そんなのは既製品として用意されてる。 importを眺めて、どんなのが利用されてるか知っておくのが先決。 Repl.hsと言うもろな名前にある。

import System.Console.Haskeline
    ( defaultSettings, getInputLine, outputStrLn, runInputT, InputT )

これを手がかりに、hoogleしたら

import System.Console.Haskeline

main :: IO ()
main = runInputT defaultSettings loop
   where
       loop :: InputT IO ()
       loop = do
           minput <- getInputLine "% "
           case minput of
               Nothing -> return ()
               Just "quit" -> return ()
               Just input -> do outputStrLn $ "Input was: " ++ input
                                loop

こんな例が出てきた。実際は、こんなコードが使われていた。

mainLoop :: IO ()
mainLoop = runInputT defaultSettings repl

repl :: Repl ()
repl = do
  minput <- getInputLine "Repl> "
  case minput of
    Nothing -> outputStrLn "Goodbye."
    Just input -> liftIO (process input) >> repl

process :: String -> IO ()
process str = do
  res <- safeExec $ evalText $ T.pack str
  either putStrLn return res

replのループが、さりげなく >> に書き換えられている。

こうなっていると、昔を思い出すな。集積回路の仕様書を眺めると、スペック と共に、使用例が掲載されている。その例を基に実際の回路を設計する。ハー ドもソフトも一緒だな。

ついでに、safeExecなんてのを調べておくか。何となく保護回路っぽい匂いが するぞ。

safeExec :: IO a -> IO (Either String a)
safeExec m = do
  result <- Control.Exception.try m
  case result of
    Left (eTop :: SomeException) ->
      case fromException eTop of
        Just (enclosed :: LispException) -> return $ Left (show enclosed)
        Nothing                -> return $ Left (show eTop)
    Right val -> return $ Right val

やっぱり、監視機能付きで実行してみて、異常が有ったら、即座に例外をあげ るってなってる。ハードの場合は、異常が有ると、匂いがしたり、煙が出たり、 爆発したりしますからねぇ。その点、ソフトはソフトですね。甘っちょろいと も言えるな。何度もやり直しOKですから。

use parts on ghci

今度は、保護装置無しで、直接ghciから動かしてみる。完成した装置を使って いたら、効き具合が検証出来ないからね。

ghci> import Data.Text as T ( pack )
ghci> import  Eval ( safeExec, evalText )
ghci> evalText $ T.pack "(+ 3 4"
 *** Exception: Parser Error, expression cannot evaluate: "<stdin>" (line 1, column 7):
unexpected end of input
expecting "#", nil, "-", "+", digit, identifier, literal string, "'", "(" or ")"

必要な部品をimportする。そして、わざとエラーになる入力を与えた。見事に 例外が発生。

ghci> safeExec $ evalText $ T.pack "(+ 3 4"
Left "Parser Error, expression cannot evaluate: \"<stdin>\" (line 1, column 7):\
nunexpected end of input\nexpecting \"#\", nil, \"-\", \"+\", digit, identifier,
 literal string, \"'\", \"(\" or \")\""

今度は、保護機構付きで実行。ちゃんとLeftだよと、言ってきた。無様に例外 が発生するのを回避してる。

opt

コマンドラインの解析は、どうやってるか? CLIなアプリを作る場合は切実だ。 見るべきファイルは、もろにズバリのCli.hs。大半を占めているのは、 Options.Applicative と言う部品。いや、部品と言うとオイラーの場合は、 2SC372とかのトランジスタが頭に浮ぶ。この場合は、もっと機能豊富だろう。 少くとも4004ぐらいな、世界初のワンチップCPUぐらいを想像せい。使った事 がない、石は知りません。

パーサーなんで、初心者には、扱いがムズイ。アプリケーションノートが公開 されている。

optparse-applicative Quick Start

こういうのが無くちゃ、採用して貰えませんよ。十分、眩暈がするけどね。

scheme 2のパーサーは、どうなっている?

readExpr :: T.Text -> Either ParseError LispVal
readExpr = parse (contents lispVal) "<stdin>"

成功すればS式の内部表現が返り、失敗すればパースエラーって風になってる。

ghci> import Parser ( readExpr, readExprFile )
ghci> :t readExpr "(+ 3 4)"
readExpr "(+ 3 4)" :: Either Text.Parsec.Error.ParseError LispVal
ghci> readExpr "(+ 3 4)"
Right (+ 3 4)

内部表現の確認。

ghci> import LispVal ( LispVal(List, Bool, Nil, Number, String, Atom) )
ghci> :t List [Number 3, String "abc", Atom "fact", Nil, Bool True]
List [Number 3, String "abc", Atom "fact", Nil, Bool True]
  :: LispVal
ghci> List [Number 3, String "abc", Atom "fact", Nil, Bool True]
(3 "abc" fact '() #t)
hashVal :: Parser LispVal
hashVal = lexeme $ char '#'
  *> (char 't' $> Bool True
  <|> char 'f' $> Bool False
  <|> char 'b' *> (Number <$> intRadix (2, oneOf "01"))
  <|> char 'o' *> (Number <$> intRadix (8, octDigit))
  <|> char 'd' *> (Number <$> intRadix (10, digit))
  <|> char 'x' *> (Number <$> intRadix (16, hexDigit))
  <|> oneOf "ei" *> fail "Unsupported: exactness"
  <|> char '(' *> fail "Unsupported: vector"
  <|> char '\\' *> fail "Unsupported: char")

schemeのリーダーの領分。#に続いてxが見付かると、それは16進数です。とか、 e表現は、浮動小数点数、iは複素数の表現成分なんで、まだサポートしてませんです、なんてのが表 現されている。

Repl> (+ #x10 #o1)
17
Repl> (+ #x10 #o9)
Parser Error, expression cannot evaluate: "<stdin>" (line 1, column 11):
unexpected "9"
expecting "-", "+" or octal digit

オクタルな数字に9は、有りません。ちゃんとチェックしてるね。

Haskellでコマンドラインアプリケーションを作る時の基本的な情報とTips

Haskellでオンラインジャッジに取り組むときの入出力(前編:標準入力の受け方)

others