stack

Table of Contents

最近haskellにべったりなので、他の世界では、どう見られているか視察して みた。 30分でわかるJavaScriptプログラマのためのモナド入門

stack

最近のstackとcabalについて簡単に なんてのを横目で睨みながらstackに手を出してみる。

curl -sSL https://get.haskellstack.org/ | sh
stack setup
stack update

こんな風にして、導入する。

Stack has been installed to: /usr/local/bin/stack

Since this installer doesn't support your Linux distribution,
there is no guarantee that 'stack' will work at all!  You may
need to manually install some system info dependencies for GHC:
  gcc, make, libffi, zlib, libgmp and libtinfo
Please see http://docs.haskellstack.org/en/stable/install_and_upgrade/
Pull requests to add support for this distro would be welcome!

WARNING: '/home/sakae/.local/bin' is not on your PATH.
    Stack will place the binaries it builds in '/home/sakae/.local/bin' so
    for best results, please add it to the beginning of PATH in your profile.

基本的には、.stackの中に全部はいるんだけど、単独のアプリは、.local/bin/ に、置かれるんで、パスを通しておいてねって注意が出てきた。説明書は、こ ちらだ。 https://docs.haskellstack.org/en/stable/GUIDE/

動作試験を兼ねて、hlint – haskell家庭教師を招聘。

[sakae@arch ~]$ stack install hlint
 :
hlint                            > [1 of 1] Compiling Main
hlint                            > Linking .stack-work/dist/x86_64-linux-tinfo6/Cabal-3.6.3.0/build/hlint/hlint ...
hlint                            > copy/register
hlint                            > Installing library in /home/sakae/.stack/snapshots/x86_64-linux-tinfo6/b5cec80aaf1f92ada03d7b9b77ffe0726b9da35ae9303c73836eddd4903edea1/9.2.8/lib/x86_64-linux-ghc-9.2.8/hlint-3.4.1-HJTLCCYJuRACwU5ZyRXB5y
hlint                            > Installing executable hlint in /home/sakae/.stack/snapshots/x86_64-linux-tinfo6/b5cec80aaf1f92ada03d7b9b77ffe0726b9da35ae9303c73836eddd4903edea1/9.2.8/bin
hlint                            > Registering library for hlint-3.4.1..
Copying from
/home/sakae/.stack/snapshots/x86_64-linux-tinfo6/b5cec80aaf1f92ada03d7b9b77ffe0726b9da35ae9303c73836eddd4903edea1/9.2.8/bin/hlint
to /home/sakae/.local/bin/hlint.
Completed 68 action(s).
Copied executables to /home/sakae/.local/bin/:
 * hlint

初回だから、結構色々な物がインストールされるんで、時間がかかる。

stackage

次は、stackの一番の注目ポイント、喧嘩がない平和なライブラリィーの総本 山を視察。

https://www.stackage.org/

によれば、最新のghc対応は 9.4.5 っぽい。安定版は、9.2.8 とな。もっと進 んでいるかと思ったら、以外に保守的だった。

気になるのは、地獄が発生しない様に調整されたパッケージ群の構成。どうなっ てるか、確認してみたい。

[sakae@fb /tmp]$ w3m -dump https://www.stackage.org/lts-20.25 >LOG

ってな具合に9.2.8のログを採取。3054個が登録されてる。hackageには確か 13000個ぐらい登録されてたので、全体の1/4ぐらいの割合か。まあ、3000個も あれば、十分と思うぞ。

これの選定って、どういう過程を経て、誰が決定してるの? こういう事に言 及する人は居ないのか。天下り的に使うだけでいいの? お前が調べろと言わ れそうであります。

stackage/CURATORS.md 内部の人のワークフローが出てた。これから想像してください、かな。一度、 stackageに引き入れられたパッケージ作者は、協力を強いられるとな。まるで、 EU連盟かNATO同盟と一緒だな。 積み重ねで、信頼されてます、それがstackage!

Add your package

以下の条件を満たすすべてのパッケージを歓迎します。

    パッケージの作成者/保守者は、保守者契約に同意します。
    パッケージは Hackage からビルドおよびテストできます。パッケージが誤って不完全になる
    ことがないようにするStack Travis スクリプトをお勧めします。
    このパッケージは、すべての依存関係の最新バージョンと互換性があります
    ( http://packdeps.haskellers.com/feed?needle=PACKAGENAMEにアクセスすると、制限の上限を確認できます)。
    このパッケージは、GHC に同梱されているライブラリのバージョンと互換性があります (寛大な下限についての詳細はこちら)。

パッケージを追加してテストする方法の詳細については、保守契約を参照してください。

注: パッケージが Hackage にアップロードされてから、Github ワークフロー アクションで上限を確認
できるようになるまでには、約 30 分の遅延があります。古いバージョンを使用しているために
プル リクエストが失敗としてマークされた場合は、PR を閉じて再度開き、Travis ビルドを再トリガーしてください。

https://bing.com に、haskell stackageの選定基準 を聞いてみた。

HaskellのパッケージマネージャーであるStackは、Stackageというパッケージセットを提供しています。
Stackageは、Hackageのパッケージを安定したバージョンでまとめたもので、Haskellの開発者が
安定した環境で開発を行うことができます1。
Stackageの選定基準は、以下のようになっています1。

・ パッケージが安定していること
・ パッケージが互換性を持つこと
・ パッケージがドキュメントを持つこと
以上の基準を満たすパッケージがStackageに含まれます。1

参考文献: 1: Stackage - HaskellWiki

詳細情報として、

https://wiki.haskell.org/Stackage

Haskellの環境構築2023

Haskellのビルドツール"stack"の紹介

次は、ChatGPTかな。https://chat.openai.com/auth/login

HaskellのStackageは、Haskellのパッケージエコシステムであり、安定して互換性のあるパッケージのセットを提供します。
Stackageの選定基準は、以下の要素に基づいています。

1. パッケージの安定性: Stackageは、安定して動作することが期待されるパッケージのセットを提供します。
パッケージのメンテナンス状況やバグ報告の頻度、メンテナの信頼性など、パッケージの安定性に関連する要素が考慮されます。

2. 互換性: Stackageは、パッケージ間の互換性を重視しています。セットに含まれるパッケージは、互いに依存関係が解決され、一緒に動作することが期待されます。
これにより、開発者は互換性の問題を最小限に抑えながら、パッケージを組み合わせて使用できます。

3. バージョン管理: Stackageは、特定のバージョンのパッケージを提供します。新しいバージョンのパッケージが安定しているかどうかを確認するために、
パッケージのリリースポリシーやテストカバレッジなどの情報が考慮されます。バージョン管理により、開発者は安定して互換性のある状態を維持できます。

4. テストスイート: パッケージが適切にテストされているかどうかも重要な要素です。Stackageは、テストスイートが充実しているパッケージを選択します。
テストスイートには、ユニットテスト、統合テスト、プロパティベースのテストなどが含まれます。

5. ユーザフィードバック: 開発者コミュニティからのフィードバックや要望もStackageの選定基準の一部です。
ユーザのニーズや要求に対応するために、パッケージの人気度や使用状況も考慮されます。

これらの基準に基づいて、Stackageは定期的に更新され、安定性と互換性のあるパッケージセットを提供します。

first run

自前でやってみる。

[sakae@arch tmp]$ stack new hoge simple
  :
Initialising Stack's project-level YAML configuration file using snapshot
https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/20/25.yaml.
Considered 1 user package.
Writing configuration to hoge/stack.yaml.
Stack's project-level YAML configuration file has been initialised.

簡単なパッケージの雛形を作成。

[sakae@arch tmp]$ tree hoge/
hoge/
├── CHANGELOG.md
├── LICENSE
├── README.md
├── Setup.hs
├── hoge.cabal
├── src
│   └── Main.hs
└── stack.yaml

cabalと同じだな。simpleを付けないと、libも開発する風になる。 ああ、cabalって、秘密結社とか陰謀団という意味深な語句なのね。

[sakae@arch hoge]$ stack build
 :
[sakae@arch hoge]$ stack run
hello world

以上で試運転終了。

haskell-mode

続いてstack用のemacs設定。

;; haskell with stack
(eval-after-load "haskell-mode"
  '(progn
     (define-key haskell-mode-map (kbd "C-c C-t") 'haskell-process-do-type)
     (define-key haskell-mode-map (kbd "C-c C-c") 'haskell-compile)
     (define-key haskell-mode-map (kbd "C-c C-z") 'haskell-interactive-switch)
     (define-key haskell-mode-map (kbd "C-c C-l") 'haskell-process-load-file)))
(setq haskell-process-type 'stack-ghci)
(setq haskell-process-path-ghci "stack")
(setq haskell-process-args-ghci "ghci")

タイプの確認が、何気に便利。毒されてきたなあ。

gnuplot

上のstackageのカタログを見ていたら、gnuplotのバインディングがあったの で、試してみたい。まだ、修行の身だからバックグランドが良く分っている物 を題材にするのがよい。

想像するに、gnuplotとの接続はパイプかな? それともスクリプトを渡すのか な。そりゃ、多分パイプでしょう。その法が面白見がありますから。

提供元は、こちら。 gnuplot: 2D and 3D plots using gnuplot

gnuplot.cabal

DescriptionにREADME相当が記述されてた。その中で興味を引かれたのは、

> $ cabal install -fbuildExamples gnuplot

これで、デモ用のアプリを作成できるとな。*.cabalはMakefile相当なので 、その気で見ていくと

Executable gnuplot-demo
  If flag(buildExamples)
    Build-Depends:
      gnuplot,
      time,
      array,
      filepath,
      utility-ht,
      base
  Else
    Buildable: False
  Default-Language: Haskell98
  GHC-Options: -Wall
  Main-Is: src/Demo.hs
  Other-Modules: Paths_gnuplot
  Autogen-Modules: Paths_gnuplot

-fはMakefileで言うタスク指定なのか。

Makefile

なんと懐かしいMakefileが同梱されてた。

ghci:   ghci-tmp

ghci-tmp:
        ghci -Wall -i:src:dist/build/autogen:execute/tmp src/Graphics/Gnuplot/Simple.hs

ghci-pipe:
        ghci -Wall -i:src:dist/build/autogen:execute/pipe src/Graphics/Gnuplot/Simple.hs

どんなエラーになるか、とりあえずのrun.

sakae@deb:/tmp/gnuplot-0.5.7$ make
ghci -Wall -i:src:dist/build/autogen:execute/tmp src/Graphics/Gnuplot/Simple.hs
Loaded package environment from /home/sakae/.ghc/i386-linux-9.2.8/environments/default
GHCi, version 9.2.8: https://www.haskell.org/ghc/  :? for help
Loaded GHCi configuration from /home/sakae/.ghci
[ 1 of 23] Compiling Graphics.Gnuplot.Execute ( execute/tmp/Graphics/Gnuplot/Execute.hs, interpreted )

execute/tmp/Graphics/Gnuplot/Execute.hs:4:1: error:
    Could not load module ‘System.IO.Temp’
    It is a member of the hidden package ‘temporary-1.3’.
    You can run ‘:set -package temporary’ to expose it.
    (Note: this unloads all the modules in the current scope.)
    Use -v (or `:set -v` in ghci) to see a list of the files searched for.
  |
4 | import System.IO.Temp (withSystemTempFile, )
  | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Failed, no modules loaded.

このエラーは昔良くみたな。やはり *.cabalのお世話にならないと駄目なんだ な。なお、 -iで検索PATHの指定が(:区切りで複数)できるのね。

[sakae@arch gnuplot-0.5.7]$ ghci --show-options | wc -l
1260
[sakae@arch gnuplot-0.5.7]$ ghci --show-options | fgrep -- '-X' | wc -l
268

コマンド・オプション有り杉。pragma相当も有り過ぎ。ご都合主義で、要求を 取り入れて行ったら、こうなりました。

とりあえず、どんなコードか眺めておく。

simple program options =
   withSystemTempFile tmpScript $ \path handle -> do
      IO.hPutStr handle (unlines program)
      IO.hClose handle
      -- putStrLn $ showCommandForUser "gnuplot" (options ++ [path])
      (exitCode, _out, _err) <-
         readProcessWithExitCode "gnuplot" (options ++ [path]) []
      -- putStr out
      -- putStr err
      return exitCode

in ghci

ghciからシンプルに使う例が載ってた。

Graphics.Gnuplot.Simple> plotFunc [] (linearScale 1000 (-10,10::Double)) sin

どんな意味があるの?

ghci> :i plotFunc
plotFunc ::
  (Atom.C a, Tuple.C a) => [Attribute] -> [a] -> (a -> a) -> IO ()
        -- Defined at src/Graphics/Gnuplot/Simple.hs:234:1

良く出てくる (linearScale 1000 (-10,10::Double)) は、どういう意味があ る?

linearScale :: Fractional a => Integer -> (a,a) -> [a]
linearScale n (x0,x1) =
   map (\m -> x0 + (x1-x0) * fromIntegral m / fromIntegral n) [0..n]

定義はこんな風だった。実験してみる。

ghci> linearScale 5 (0.0,1.0::Double)
[0.0,0.2,0.4,0.6,0.8,1.0]

分ってしまえば、こんなんでもOK.

ghci> plotFunc [Title "Test"] (linearScale 100 (-10,10::Double)) sin
ghci> plotFunc [Title "Test"] [0.0,0.1..10.0::Double] sin
ghci> plotFunc [] (linearScale 1000 (-10,10::Double)) sin(x)/x

<interactive>:1:53: error: Variable not in scope: x

媒介変数を使った描画は、訳あって出来ません、だな。

Atom.Cとかは、何物? Graphics/Gnuplot/Value.Atom.hs に定義されてた。

data OptionSet a =
   OptionSet {
      optData :: [String],
      optFormat :: [String],
      optOthers :: [(Option.T, [String])]
   }

class C a where
   options :: OptionSet a
   options =
      OptionSet [] [{- quote "%g" -}] []

オプションの塊をまとめた物なのね。

by hlint

デモのコードを見ていたら、ちょいと分からないのが有った。

main :: IO ()
main = sequence_ $
   GP.plotDefault simple2d :
    :
   GP.plotDefault multiplot :
   []

ここに出てる : って、いわゆるCons なのか? 先生に聞いてみる。

sakae@deb:/tmp/gnuplot-0.5.7/src$ hlint Demo.hs
Demo.hs:(209,4)-(225,5): Suggestion: Use list literal
Found:
  GP.plotDefault simple2d
    : GP.plotDefault list2d

Perhaps:
  [GP.plotDefault simple2d, GP.plotDefault list2d,
   GP.plotDefault labels2d, GP.plotDefault candle2d,
   :
   GP.plotDefault multiplot]

ああ、スッキリした。

simple2d :: Plot2D.T Double Double
simple2d =
   Plot2D.function Graph2D.lines
      (linearScale 100 (-10,10)) sin

list2d :: Plot2D.T Int Integer
list2d =
   Plot2D.list Graph2D.listPoints [0,1,1,2,3,5,8,13]

実際の描画関数は、こんな風に定義されるのね。

もう少し探索

ghciから描画したグラフの凡例に、"/tmp/gnuplot-xxxx/curve0.csv" using 1:2 なんてのが出てる事に気付いた。長年のgnuplotユーザーはピンときたね。 データはCSVファイルに書き出して、それを使ってると。もちろん、そのデー タの1列目はX軸値で2列目はY軸値だ。ついでに想像すると、plot "carve0.csv" using 1:2 with line ってコマンドなんだろう。

上手い具合に、ファイルが残っていた。

[sakae@arch gnuplot-56171f79bb30f088]$ head -3 curve0.csv
-10.0, 0.5440211108893698
-9.98, 0.527131998452086
-9.96, 0.5100320402437544

更に/tmpの下に関係者が居た。

[sakae@arch tmp]$ cat curve423-1.gp
set title "Test"
plot  "/tmp/gnuplot-56171f79bb30f088/curve0.csv" using 1:2 with lines
pause mouse close

423てのは、起動したghciのPIDだろう。ビンゴーーーーー。気持ちいいな。

するってえと、gnuplotの起動方法まで、想像つくな。gnuplot -e curve423-1.gp

もう、教師有り学習みたいな気分だな。

runGnuplot ::
   Graph.C graph =>
   [Attribute] -> String -> Plot.T graph -> IO ()
runGnuplot attrs cmd (Plot.Cons mp) =
   void $ Cmd.asyncIfInteractive (interactiveTerm attrs) $ Cmd.run $ \dir ->
      let files = MR.runReader (MS.evalStateT mp 0) dir
      in  (map attrToProg attrs ++
           [cmd ++ " " ++
            extractRanges attrs ++ " " ++
            commaConcat (plotFileStatements files)],
           files)

plotFileStatements ::
   Graph.C graph => [Plot.File graph] -> [String]
plotFileStatements =
   concatMap
      (\(Plot.File filename _ grs) ->
         map (\gr -> quote filename ++ " " ++ Graph.toString gr) grs)

こんなのも有る。私的型推論すれば、quoteって関数は、単語の両端をダブル クォートすると予測される。

attrToProg (Title  title_)       = "set title " ++ quote title_
attrToProg (XLabel label)        = "set xlabel " ++ quote label

同名の関数が多数ある。しかし、引数の型が皆違う。世に言うマルチメソッドっ て奴なんだな。haskellでは、構える事なく、パターン照合で済ませている。 強力な機能があたり前に利用されてる。

だからと言う訳ではなく、非常に多数の型を作成してる。何でも型にしとけ、 そうすれば、後で救われるぞ、って事です。

上の例だと、普通はタイトルなんてわざわざ型を建てる必要なんだろって考え ちゃうけど、それはまだhaskell脳になってない証拠。大文字始まりだと、型 だからね、その積りでコード嫁。


This year's Index

Home