Write You A Scheme, Version 2

Table of Contents

myadd

小学校低学年用の、足し算を定義してみた。+ は、既に予約されてるんで、 ちょっと太めの記号になってます。

λ>  3 <+> 4
Left "Use both hands and legs"
λ>  3 <+> 22
Left "Use your brain"
λ>  21 <+> 33
Right 54
λ>  1.2 <+> 5.5
<interactive>:17:1-3: error:
    • No instance for (Fractional Int) arising from the literal ‘1.2’
    • In the first argument of ‘(<+>)’, namely ‘1.2’
      In the expression: 1.2 <+> 5.5
      In an equation for ‘it’: it = 1.2 <+> 5.5

小さい数同士なら、両手両足の指を使いましょう。片方だけ小さいなら、暗算 しましょう。それ以外なら、ちゃんと正しい答を教えてあげます。少数の計算 は、許しませんからね。

種明かし

(<+>) ::  Int -> Int -> Either String Int
(<+>) x y
  | x < 10 && y < 10 = Left "Use both hands and legs"
  | x < 10 || y < 10 = Left "Use your brain"
  | otherwise        = Right (x + y)

ここで使ったEitherの詳しい説明は、こちら HaskellのEitherについて

λ>  import Data.Ratio
λ>  1%2 + 1%3
5 % 6

分数と言うか有理数は、普段使わないけど、モジュールをロードすれば、有効 になります。

λ>  import Data.Complex
λ>  :t 1 :+ 2
1 :+ 2 :: Num a => Complex a
λ> a = 1.0 :+ 1.0
λ>  magnitude a
1.4142135623730951
λ> phase a
0.7853981633974483
λ>  polar a
(1.4142135623730951,0.7853981633974483)
λ>  exp (0.0 :+ pi)
(-1.0) :+ 1.2246467991473532e-16

分数よりは、複素数の方が使いそうだな。

devel bed for scheme

write-you-a-scheme-v2 / scheme

ここから、一式をDLできる。cabalにもstackにも対応してるから便利。なんだ けど、そのせいで、グチャグチャしてる。余計な気配りは脳に負担をかけるの で、シンプルな構成にしたい。cabal専用で、コード閲覧に適したものね。

その前に、こんな資料も提供されてるので、見ておけ。

sakae@deb:/tmp/scheme/sources$ ls -sk
total 51112
  388 AbstractDefinitionalInterpreters.pdf
  132 pearl-parser.pdf
  756 r5rs.pdf
47892 Scheme-and-the-Art-of-Programming-Springer-Friedman.pdf
  240 Transformers.pdf
 1624 Write_Yourself_a_Scheme_in_48_Hours.pdf
   40 WYAS-Dependency-Tree.png
   40 WYAS-Lisp-Interpreter-Steps.png

ご丁寧にschemeの仕様書 R5RS が収録されてるよ。

まずは、プロジェクト名ね。今回は2文字にする。 mkdir cs; cd cs; cabal init -i して、骨格を作成。

配置

構成は、こんな風にした。

[sakae@fb /tmp/cs]$ tree .
.
├── CHANGELOG.md
├── LICENSE
├── app
│   ├── Cli.hs
│   ├── Eval.hs
│   ├── LispVal.hs
│   ├── Main.hs
│   ├── Parser.hs
│   ├── Prim.hs
│   └── Repl.hs
├── cs.cabal
└── lib
    └── stdlib.scm

appの中へhaskellファイルを全部ぶちこみ、schemeファイルは、一応分離。 cabal用のMakefileは必須。ライセンスとログファイルは形だけでも用意して おかないと、インストール時に文句を言われる。

cs.cabal

実は、これが肝になる。こんな風になった。

cabal-version:      3.0
name:               cs
version:            0.1.0.0
synopsis:           scheme
license:            MIT
license-file:       LICENSE
build-type:         Simple
extra-doc-files:    CHANGELOG.md
common warnings
    ghc-options: -Wall
    default-extensions:
      OverloadedStrings
      StrictData

executable cs
    import:           warnings
    main-is:          Main.hs
    other-modules:    Cli, Eval, LispVal, Parser, Prim, Repl
    build-depends:    base ^>=4.16.4.0
                    , text
                    , containers
                    , mtl
                    , optparse-applicative
                    , parsec
                    , directory
                    , HTTP
                    , haskeline
    hs-source-dirs:   app
    default-language: Haskell2010

build-dependsの中でHTTPなんて、全く関係なさそうな物が入っている。どう やら、parsecが必要としてるっぽい。

それより苦労したのは、OverloadedStringsの指定。これがないと、 LispVal.hsの中で使用してる、import qualified Data.Text as T で、型が合 わないエラーが頻発。こんなGHCの都合なんて、知らんわ。 何故こんな、トンデモ・オプションに目が行ったかというと、すごいH本の付 録に解説がでてたから。

haskellの標準はStringなんだけど、これは1文字に4バイトを要求する効率の 悪い格納方法を取っている。そのかわり素直なんだけどね。で、これに変る効 率のよいのが、Textパッケージ。実用の世界ですなあ。なんでも理想的に行く 訳はない。で、理想と現実を埋める方策がGHCに用意されたんだ。

{-# LANGUAGE OVerlodingStrings #-}

ってのをファイルの冒頭に追加するのがいいんだけど、面倒なので一括有効に するって訳だ。こういう事にも出喰わすんで、いろいろやってみるものだ。

run

[sakae@fb /tmp/cs]$ cabal run -- cs -r
[1 of 7] Compiling LispVal          ( app/LispVal.hs, /tmp/cs/dist-newstyle/build/i386-freebsd/ghc-9.2.7/cs-0.1.0.0/x/cs/build/cs/cs-tmp/LispVal.o )
[2 of 7] Compiling Parser           ( app/Parser.hs, /tmp/cs/dist-newstyle/build/i386-freebsd/ghc-9.2.7/cs-0.1.0.0/x/cs/build/cs/cs-tmp/Parser.o )
[3 of 7] Compiling Prim             ( app/Prim.hs, /tmp/cs/dist-newstyle/build/i386-freebsd/ghc-9.2.7/cs-0.1.0.0/x/cs/build/cs/cs-tmp/Prim.o )
[4 of 7] Compiling Eval             ( app/Eval.hs, /tmp/cs/dist-newstyle/build/i386-freebsd/ghc-9.2.7/cs-0.1.0.0/x/cs/build/cs/cs-tmp/Eval.o )
[5 of 7] Compiling Repl             ( app/Repl.hs, /tmp/cs/dist-newstyle/build/i386-freebsd/ghc-9.2.7/cs-0.1.0.0/x/cs/build/cs/cs-tmp/Repl.o )
[6 of 7] Compiling Cli              ( app/Cli.hs, /tmp/cs/dist-newstyle/build/i386-freebsd/ghc-9.2.7/cs-0.1.0.0/x/cs/build/cs/cs-tmp/Cli.o )
[7 of 7] Compiling Main             ( app/Main.hs, /tmp/cs/dist-newstyle/build/i386-freebsd/ghc-9.2.7/cs-0.1.0.0/x/cs/build/cs/cs-tmp/Main.o )
Linking /tmp/cs/dist-newstyle/build/i386-freebsd/ghc-9.2.7/cs-0.1.0.0/x/cs/build/cs/cs ...
Repl> (cons 1 2)
(1 2)

一度buildしてあれば、こういう起動方法でもいいとな。installしないで、し た積りで実行する方法だな。

sakae@deb:/tmp/cs$ cabal exec -- cs -r
Repl> (cdr '(a b c))
(b c)
Repl> (cddr '(a b c))
Error Unbound Variable: b
Repl> (cddr '(a + c))
Error Unbound Variable: c
Repl> (cddr '(a + *))
Error Type Mismatch: numeric op (internal function)

面白いbug発見。gauche先生、正解を頼むわ。

gosh$ (cddr '(a b c))
(c)

:etags

前回xmonadの所でやった、ghciのコマンドである、:etagsの実行ができた。

[sakae@fb /tmp/cs]$ wc TAGS
      61      95    1462 TAGS

試運転で、main = Cli.cliIface の、Cli.cliface のCに照準を合わせると

cliIface :: IO ()
cliIface = execParser opts >>= schemeEntryPoint
  where
    opts = info (helper <*> parseLineOpts)
      ( fullDesc
     <> header "Executable binary for Write You A Scheme v2.0"
     <> progDesc "contains an entry point for both running scripts and repl" )

の、型宣言に飛んできた。どうやら、動いてるっぽい。 と糠喜びであった。この先は知らないと言う。

出来たTAGを眺めて気付いたぞ。

/tmp/cs/app/Main.hs,15
main^?main^A5,33
^L
app/Cli.hs,26
cliIface^?cliIface^A57,1577
^L
app/Eval.hs,289
basicEnv^?basicEnv^A50,1259
safeExec^?safeExec^A62,1675
runASTinEnv^?runASTinEnv^A72,2014
evalFile^?evalFile^A79,2243
fileToEvalForm^?fileToEvalForm^A82,2389
runParseTest^?runParseTest^A85,2540
getFileContents^?getFileContents^A102,3030
textToEvalForm^?textToEvalForm^A107,3208
evalText^?evalText^A110,3335
 :

モジュールで公開してる関数、data,newtype,instanceがTAGSに掲載されて るんだ。未公開情報は、見ちゃダメよってのを忠実に守っている。

GHC屋さんへのリクエスト -> なんでもTAGSしちゃうSWを付けて下さい。 それまでは、

[sakae@arch app]$ ctags -e *.hs
[sakae@arch app]$ wc TAGS
  222  1669 12717 TAGS

で、我慢しますから。Debianだと、どちらを試しても駄目だなあ。

sakae@deb:/tmp/cs/app$ ls -l /usr/bin/ctags-*
-rwxr-xr-x 1 root root  297316 Feb  9  2021 /usr/bin/ctags-exuberant*
-rwxr-xr-x 1 root root 1111720 Apr 24  2021 /usr/bin/ctags-universal*

ちゃんと動くArchLinux版の方。

Universal Ctags 6.0.0(p6.0.20221218.0), Copyright (C) 2015-2022 Universal Ctags Team
Universal Ctags is derived from Exuberant Ctags.
Exuberant Ctags 5.8, Copyright (C) 1996-2009 Darren Hiebert
  Compiled: Feb 24 2023, 18:05:01
  URL: https://ctags.io/

ctags –list-languages すると、色々と列挙されてるなあ。148種もある。

これでも飛んで行けない奴が多数。たとえば、

cliIface = execParser opts >>= schemeEntryPoint

execParserはダメ。でも、同一ファイル内に手掛かりが有るはず。探してみる と、

import Options.Applicative
    ( helper,
      execParser,
       :

よそのパッケージからってのが一発で判明。この場合ならhoogle様のお世話に なるべきだな。まあ、こんな具合に取り敢えずスイスイ・モードが可能になっ た。

cabal build

初回のコンパイルの様子

Resolving dependencies...
Build profile: -w ghc-9.2.7 -O1
In order, the following will be built (use -v for more details):
 - ansi-terminal-1.0 (lib) (requires download & build)
 - hsc2hs-0.68.9 (exe:hsc2hs) (requires download & build)
 - prettyprinter-1.7.1 (lib) (requires download & build)
 - th-compat-0.1.4 (lib) (requires download & build)
 - network-3.1.4.0 (lib:network) (requires download & build)
 - prettyprinter-ansi-terminal-1.1.3 (lib) (requires download & build)
 - network-uri-2.6.4.2 (lib) (requires download & build)
 - optparse-applicative-0.18.0.0 (lib) (requires download & build)
 - HTTP-4000.4.1 (lib) (requires download & build)
 - cs-0.1.0.0 (exe:cs) (first run)
Downloading  th-compat-0.1.4
Downloaded   th-compat-0.1.4
Downloading  hsc2hs-0.68.9
  :

これを見ると、最終目標の cs を作成するために、関連品をインストールする のが、良くわかる。一度インストールされるとcabalをアップデートする等を しない限りDLとかはされない。裏で色々な物が必要って肌で感じられる場面で す。

で、schemeを作成するのに、HTTPなんて必要なの。 build-dependsの設定をそ のままコピペしてたんだった。せいぜい好意的に解釈して、docを作るのに必 要ぐらいに思っていた。でも、それが正しいか自信なし。さあ、実験開始。

やる事は簡単で、build-dependsからHTTPを外してから、cabal build

   :
[3 of 7] Compiling Prim             ( app/Prim.hs, /tmp/cs/dist-newstyle/build/x86_64-linux/ghc-9.2.7/cs-0.1.0.0/x/cs/build/cs/cs-tmp/Prim.o )

app/Prim.hs:17:1: error:
    Could not load module ‘Network.HTTP’
    It is a member of the hidden package ‘HTTP-4000.4.1’.
    Perhaps you need to add ‘HTTP’ to the build-depends in your .cabal file.
    It is a member of the hidden package ‘HTTP-4000.4.1’.
    Perhaps you need to add ‘HTTP’ to the build-depends in your .cabal file.
    Use -v (or `:set -v` in ghci) to see a list of the files searched for.
   |
17 | import Network.HTTP ( getRequest, getResponseBody, simpleHTTP )
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

この有り難い神託より、色々な事がわかる。隠れパッケージが必要だから追加 してね。で、疑問なのは、schemeの原始関数を定義してる部分で欲してる事。 考えるより、ソースだ。importしてる関数をどう使っているかだな。

openURL :: T.Text -> IO LispVal
openURL x = do
  req  <- simpleHTTP (getRequest $ T.unpack x)
  body <- getResponseBody req
  return $ String $ T.pack body

ただのschemeじゃ現代の若者に受け入れられないので、大盤振舞いで、Webに もアクセスできる奴を、根底に用意しましたとな。lispは、 cons,car,cdr,atom,eqぐらいが有れば構築できるって時代じゃないわけか。

実際はこの関数が生では使われておらず、一枚ラッパーを纏っている。

Repl> (wslurp "http://www.yahoo.co.jp")
" "
cs: mmap 4096 bytes at (nil): Cannot allocate memory
cs: Try specifying an address with +RTS -xm<addr> -RTS
Segmentation fault (core dumped)

マルチコアでスケールするようになったHaskell

を見ると、RTSで前後を挟んで、オプションを設定するんだな。で、そのオプ ションは、メモリーのサイズなんだろうね。Real World Haskell の FFIの章 あたりに、説明があるかな?

GHC compile フラグの一覧表

GHC コンパイル済みプログラムの実行

+RTS -xm20000000 -RTS メモリーたっぷり頂戴なのかな。

[sakae@arch cs]$ cabal exec -- cs +RTS -xm20000000 -RTS -r
cs: Most RTS options are disabled. Link with -rtsopts to enable them.

さあ、どうする?

emacs split

xmonadで広い画面が獲得出来る様になったんで、emacs画面の分割方向を変更 したい。どうやるねん?

How to change the default split-screen direction?

Use (setq split-width-threshold nil) split upper and lower
Use (setq split-width-threshold 1 )  split left and right

C-x 3 を使うと、手動で左右に分割とな。

xmonadで複数画面、そのひとつでtmux、その中で画面分割、そしてemacsを動 かして、更に分割。画面分割の三層構造が実現できるな。


This year's Index

Home