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)
を見ると、RTSで前後を挟んで、オプションを設定するんだな。で、そのオプ ションは、メモリーのサイズなんだろうね。Real World Haskell の FFIの章 あたりに、説明があるかな?
+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を動 かして、更に分割。画面分割の三層構造が実現できるな。