HaskellでGTK 4 tutorialを進めている

GTK 4 tutorial という GTK についての充実したチュートリアルがある。(英語だが日本の方が作っているみたいだ。)

toshiocp.github.io

何か GUI アプリケーションを作ってみたい気分だったので、冬休み自由研究としてこちらのサンプルコードを Haskell で書いてみるということをしていた。

Haskell で GTK アプリが書けるのかというと、haskell-gi という GTK のバインディングがある。これは単純に GTK の C API をラップするだけでなく、モダンな Haskell の開発体験ができるようによく考えられて作られているようだ。

github.com

これを用いて、現在 GTK 4 tutorial の Section 8 までを Haskell で記述できた。コードは GitHub に置いておくが、いろいろ知見があったのでここで整理しておきたい。

コードはこちら: 

github.com

目次:

2種類の記法

haskell-gi では C の関数を直接的にラップした API に加えて、より高度な API も提供している。

例えば Gtk.Application を作成する API は以下のように呼び出せる。

import GI.Gtk qualified as Gtk
import GI.Gio qualified as Gio

main :: IO ()
main = do
  app <- Gtk.applicationNew 
      (Just "application-id") 
      [Gio.ApplicationFlagsHandlesOpen]
  pure ()

これは GTK の gtk_application_new 関数を直接的にラップしたものになっている。

Gtk.Application.new

上記のコードは次のように別の方法でも書ける。

import Data.GI.Base (AttrOp ((:=)), new)
import GI.Gtk qualified as Gtk
import GI.Gio qualified as Gio

main :: IO ()
main = do
  app <- new Gtk.Application 
      [ #applicationId := "application-id",
        #flags := [Gio.ApplicationFlagsHandlesOpen]
      ]
  pure ()

こちらは GObject が前面に出る API になっている。

GObject というのは、もとは GTK の一部だったが現在は独立した、C言語にオブジェクト指向的な型システムを導入するライブラリのようだ。

こちらのように GObject にプロパティを設定していくのがより GTK 4 的な書き方になるらしい。

GObjectの操作

GObject の操作を覚えることで、Haskell で GTK アプリケーションを作れるようになっていく。

import Data.GI.Base (AttrOp ((:=)), new, set, get, on)
...
main = do
  app <- new Gtk.Application [#applicationId := "application-id"]
  -- `on` によってシグナルが送られたときのコールバックを設定できる
  -- ここでは app に #activate シグナルが送られたとき
  _ <- on app #activate do
    -- callback では ImplicitParams 拡張によってレシーバー `?self` を参照できる
    -- `get` によってプロパティを参照できる
    putStrLn $ show (?self `get` #applicationId`)
    win <- new Gtk.ApplicationWindow [#application := ?self]
    -- `set` によってプロパティを設定できる
    win `set` [#title := "window title"]
    win `set` [#defaultWidth := 400, #defaultHeight := 300]
    -- OverloadedRecordDot 拡張によるメソッド呼び出し風の記法も使える
    win.present
  pure ()

haskell-gi (GObject バインディングを含む) はいくつかの近年の言語拡張を取り入れておりこういう書き方が可能になっている。 入門書でしか Haskell を知らない人はちょっとびっくりするかもしれない。

特に重要なのは OverloadedLabels/OverloadedRecordDot 拡張で、 import していない識別子 (# で始まるものと .present) が使えるのはこのためだ。

User Data の使い方

GObject のインスタンスには user data といって任意のデータを紐付けておくことができる。 何かのシグナルが発生したときに好きな値でコールバックを呼ぶといった用途で使うようだ。

haskell-gi では Haskell の Foreign.Ptr を user data に紐付けられるようになっているので、 Haskell 上の任意のデータを GObject に持たせられる。

例えば以下のようにデータ構造 MyViewState とポインタ操作のための便利関数を用意する。

import Data.Text qualified as T
import Foreign.Ptr qualified as P
import Foreign.StablePtr qualified as P
...
data MyViewState = MyViewState
  { counter :: Int
  }

myViewStateKey :: T.Text
myViewStateKey = "myview.state"

newMyViewStatePtr :: MyViewState -> IO (P.Ptr ())
newMyViewStatePtr = (fmap P.castStablePtrToPtr) . P.newStablePtr

deRefMyViewStatePtr :: P.Ptr () -> IO MyViewState
deRefMyViewStatePtr = P.deRefStablePtr . P.castPtrToStablePtr

freeMyViewStatePtr :: P.Ptr () -> IO ()
freeMyViewStatePtr = P.freeStablePtr . P.castPtrToStablePtr

User Data として登録できるのは Ptr だが、ここでは StablePtr を経由している。 StablePtr は Haskell 側で作ったデータを GC で削除されないようにする効果があるので、 Haskell 外に Haskell データの参照を持たせる目的では都合がいい。 その一方で解放の責任はプログラマにある。

上記を利用すると User Data の登録や取得は以下のように書ける。

do
  tv <- new Gtk.TextView []
  let newState = MyViewState 42
  newStatePtr <- newMyViewStatePtr newState
  _ <- tv.setDataFull myViewStateKey newStatePtr $ Just freeMyViewStatePtr

...

do
  stateData <- tv.getData myViewStateKey
  state <- deRefMyViewStatePtr stateData

setDataFull は User Data の登録と同時にファイナライザを指定できる API で、 GObject が解放されたときに Haskell 側のデータも解放させられる。

取得するときは getData でポインタを得てから deRef すれば Haskell データを回復させられる。

haskell-gi ではこのように Haskell と GTK の相互運用性への様々な工夫があるようだ。

Haskellで雑にA/Bテストの検定

年に一度くらいは Haskell で A/B テストの検定をしたくなるでしょう。普段は R とか Python とか表計算ソフトで済ますにしても人生時にはそのようなゆとりも必要です。

Haskell環境の準備

PC が吹っ飛んだので Haskell 環境がありません。まず Haskell 環境から準備します。

……とは言っても GHCup を入れて質問にポチポチ回答するだけで GHC や Cabal や Stack や Haskell Language Server まですべてがインストールされていました。最近はすごいですね。

www.haskell.org

これの他には Visual Studio Code に Haskell 拡張 を入れたくらいです。

Haskellスクリプティング

今回はゴテゴテしたプロジェクト構造を組み立てたくないので1つのスクリプトファイルで完結させたいです。

そこで次のようなファイルを用意します。

gist3067f1bf08254dacf8752005ee4fe757

このようなファイルはスクリプト的に実行できます。

> chmod u+x hello.hs
> ./hello.hs
Hello, World

便利なものですね。

A/Bテストの設定

準備が整ったので本題の A/B テストに入っていきます。

A/B テストの設定は次のようにします。

  •  今まで通りの対照群と、デザイン変更を行った実験群を用意する。
  • 実験群へはトラフィックの10%を分配する。
  • インプレッション (表示) に対してクリックが発生したか否かをカウントし、独立性のカイ二乗検定で有意な改善が見られたか確認する。

この設定のもとで次の結果が得られたとします。

  インプレッション クリック
対照群 90000 900
実験群 10000 125

ざっと対照群がクリック率1%、実験群がクリック率1.25%となりました。デザイン変更によってクリック率が改善したように見えますが、本当にそうか確かめてみましょう。

Haskellでカイ二乗検定

Haskell でカイ二乗検定を行います。

statistics というそれっぽいライブラリがあるのでこれを使ってみましょう。

hackage.haskell.org

丁度よくこのライブラリはカイ二乗検定の関数を提供していました。

chi2test type annotation from https://hackage.has…

コメントを読む限り、第1引数に自由度、第2引数に実測値&期待値ペアのリストを与えれば検定結果を返してくれるようです。

この関数を利用すればよさそうですが、注意すべき点が2つあります。

1つ目は、第1引数はカイ二乗分布の自由度ではない点です。詳しくは後述しますが今回のユースケースでは "2" を与えればOKです。

2つ目は、この関数は独立性のカイ二乗検定ではなく適合度のカイ二乗検定の形になっている点です。独立性の検定になるように引数を調整する必要があります。

前置きが長くなりましたがスクリプト全体は以下になります。

gist13a1ca9dae320527d83b577e8b13375b

5行目の LANGUAGE プラグマは全然要りません。qualified import の語順を変えるためだけに置いています。

こちらを実行すると次の結果が得られます。

> ./chisq.hs
Just (Test {testSignificance = mkPValue 1.8537396774127135e-2, testStatistics = 5.544637411516828, testDistribution = chiSquared 1})

自由度1のカイ二乗分布が選択され、カイ二乗値 5.54 が得られ、その p 値は 1.85% であると言っています。

クリック率が改善されたように見えるのは 1.85% の確率で偶然であり、逆に言えば 98.15% でデザイン変更が功を奏した結果というわけです。新しいデザインは成功と言えそうですね。

念のため統計ソフトに同じ値を入力して検算してみます。

> contingencyTable <- cbind(c(90000 - 900, 900), c(10000 - 125, 125))
> chisq.test(contingencyTable, correct=FALSE)

        Pearson's Chi-squared test

data:  contingencyTable
X-squared = 5.5446, df = 1, p-value = 0.01854

同じ結果が得られました。実装に問題はなさそうです。Haskell で A/B テストの検定ができたことが確認できました。

(correct=FALSE はイェーツの補正を無効化しています。この補正はサンプルサイズが十分ある場合は不要のようです。)

補足: chi2testの自由度引数の意味

chi2test 関数の第1引数はカイ二乗分布の自由度ではないと言いました。では何を意味しているのでしょうか。

statistics のコメントやコードを見ると、この引数は以下の意味があるようです。

まず m × n のテーブルがあるとき、少なくとも 1 つのセルは自由ではありません (■)。

       
       
       
      ■

また m-1 および n-1 のセルも自由ではありません (▲)。

      ▲
      ▲
      ▲
▲ ▲ ▲ ■

chi2test の第1引数にはこの (m-1) + (n-1) を与えます。

▲でも■でもないセルの数が自由度となります。

chi2test 関数は第2引数からセルの数を知ることができるので、(セルの数) - ( (m-1) + (n-1) ) - 1 からカイ二乗分布の自由度を計算しています。

ところで、自由度は (m-1)×(n-1) としている文献をよく見ます。上記の図でも空白のセルが (m-1)×(n-1) と同じであることが分かります。

参考文献

ローカルk8sの上に色んなサービスを立てて壊す

ローカルに立ち上げた Kubernetes Dashboard

モダンなサービス開発はとにかく入用のミドルウェアが多い。ちょっとしたものを作るだけで RDB と NoSQL とインメモリキャッシュと監視ダッシュボードが必要になってしまう。遠い記憶に RDB とキーバリューストアとキャッシュと検索とジョブキューをすべて MySQL にやらせていた牧歌的な時代もあったような気がするがもうそんな時代ではないのだった。

実際にサービスをデプロイする段ではなくローカルで開発するときにもそういったミドルウェアがないと開発自体が進まない。かと言って素の OS の上にミドルウェアを入れまくると設定ファイルディレクトリとかデータディレクトリが散逸してアンインストールやトラブルシュートの妨げになってしまう。

おそらくそういった複数のサービスをローカルに立ち上げるときによく使われるのは Docker Compose だと思う。まあそれが正着だと思う。様々なミドルウェアが Docker Compose のサンプルコードを提供している。

そんな Docker Compose にもちょっとした不満があって、開発の成果物として Docker image を作ったならば実際にサービスをデプロイする先は大抵 Kubernetes クラスターに変えないといけない。

(もしかしたら Docker Compose をプロダクション環境で使うケースもあるかもしれないが自分の周りにそういう雰囲気はなかった)

ローカル開発でしか使わない技術を覚えるのも腰が重いし、なら最初からローカルに Kubernetes クラスターを用意してその上でいろいろやればいいのでは、ということで作ったものがこれ。

github.com

これはローカル環境で Kubernetes を実行する方法の一つである kind を利用して様々なサービスを立ち上げたり壊したりできるものだ。

現在のところ以下のサービスを構築&破壊できる。

主要な操作は Makefile に押し込んでいる。まず、クラスター自体の作成と破棄は次のようにする。何かがCPUを食いつぶしているな、というときはクラスター全体を雑に停止できる。

gist1a1da78dc3271f98fda41b05fe858954

そのクラスターの上に各ミドルウェアを立てていく(Redisの例)。

gist59f7240b34ebea34766d95e9c9d8686a

必要であれば Kubernetes Dashboard を見ることもできる(記事上部の画像)。

gist05df9585c8076ac1accd6c27c0b06f69

その他の仕様:

 

さてこれでおよそ目的は達成できた。

  • Docker Compose よりはプロダクションに近い環境で、
  • 構築が面倒なミドルウェアを自由に作成し破棄できる。

と言いたいところだが問題点もある。

  • kind のクラスター設定がどうやら動的に変更できない。つまり公開ポートや永続化ディレクトリを追加したり変更したりするたびにクラスター全体を再作成しないといけない。

やはり Docker Compose の方がよかったかもしれない。

きりたんぽ鍋を作る

きりたんぽ鍋を作ります。

きりたんぽ鍋の具材

具材

  • きりたんぽ
  • さいとうの比内地鶏スープ
  • 鶏肉
  • ネギ
  • ゴボウ
  • マイタケ
  • 糸コンニャク
  • セリ

手順

  1. 鶏肉を手頃な大きさに切る。
  2. 鍋に水1200mlを張って火にかけ、鶏肉を水から煮始める。
  3. ゴボウをささがきにしてボウルの水につける。
  4. ゴボウをボウルから出して鍋に入れる。
  5. 比内地鶏スープを入れる。
  6. 沸騰したら糸コンニャクとマイタケを入れる。
  7. 20分ほど煮たらネギを入れる。
  8. ネギに火が通ったあたりできりたんぽを入れる。
  9. きりたんぽを煮る時間は好みで調節する(1~5分くらい?)。
  10. 最後にセリを投入する。湯気を通す程度でいい。
  11. 完成。

鶏肉とゴボウは水から

糸コンニャクとシイタケ

きりたんぽとネギの投入は後半

セリは最終盤

完成

補足

比内地鶏スープはさいとうのでなくてもいいが稀においしくないスープがある。

比内地鶏スープに含まれているから必要ではないが鶏ガラでダシを取ってもいい。

セリは三関産が望ましい。三関産の場合はセリの根も煮てよい。

きりたんぽは煮る時間が長いと崩れるのであまり長くは煮ない。きりたんぽだけ別の鍋で煮るテクニックがある。

きりたんぽ以外にも「だまこ」を入れてもいい。

「秋田県民は家ではきりたんぽ鍋を食べない」と言われて、うちでは作るけどな……となったのでこの記事を書いた。

NixによるHaskell開発環境の構築

Nix package manager によって Haskell のスクリプティングおよびパッケージ開発の環境構築をしていく。

こいついつも環境構築してんな

環境構築以外はブログに書くような一般性のあることをやっていないということで……。

ここで触れられないこと

  • cabal.project によるマルチプロジェクトを扱う方法
  • 外部の nix store を使うこと
  • nix の新しいコマンド群 (nix コマンドのサブコマンド群)
  • デプロイや公式 Docker イメージについて
  • Stack integration
  • IOHKのリソース
  • NixOS
  • その他 Nix の深い話

Nix

Nix がなにかよく分からないが様々な環境で使えるパッケージセットとして利用している。

そこだけ聞くと stackage みたいだが全然 Haskell に閉じたものではなくて普通の OS のパッケージマネージャのようなカバー範囲を持っている。 それでいて Haskell のライブラリ依存関係も相当なリソースを投入して安定に保たれているということのようだ。

自分は Mac と Ubuntu on WSL で同じ設定ファイルで Haskell の環境を構築できている。

さて Nix で検索すると NixOS というのが出てくるが OS を考えずにパッケージマネージャだけ利用することができる。

利用形態:

  • Nix package manager のみ
  • NixOS と Nix package manager

Nix ではパッケージセットを channel と呼び、これには何種類かある。
https://nixos.wiki/wiki/Nix_channels

Nix channel:

  • nixpkgs-unstable (= nixpkgs)
  • NixOS-xx.yy (NixOS-21.11 など)

NixOS channel は名前の通り NixOS 用であり Nix package manager だけのときに使うことは推奨されない。 つまり Nix package manager のみの利用のときには nixpkgs を使うことになる。 なお nixpkgs-stable という channel はないようだ。

プロジェクトの環境を固定したいときはこの channel のバージョンを固定すればいい。
https://nixos.wiki/wiki/FAQ/Pinning_Nixpkgs

スクリプティング

まず、Nix を使わなくても Haskell スクリプティングはできる。 cabal および stack のスクリプトはだめぽ氏による記事がとても参考になると思う。
https://zenn.dev/mod_poppo/scraps/e2891dbebb235d

Nix による方法は、まず Nix package manager をお使いの環境にインストールする。

https://nixos.org/download.html

するとシンプルなスクリプトは以下のように書ける (標準入力から1行読んで適当な色をつけて出力するスクリプト)。

> echo "ヤッホー" | ./yamabiko-nix.hs
ヤッホー

nix-shell は Nix の環境の中に入るためのコマンドだが、 shebang に書くことで特殊な動作をしてそのスクリプトを Nix 環境の中で実行するように動く。

コマンドライン引数は複数行に分けて記述できる。

  • -p / --packages: 依存パッケージを指定。デフォルトの channel は nixpkgs が選ばれる。
  • -i / --interpreter: このスクリプトを処理するコマンドを指定。

パッケージ指定が Nix Haskell の特徴的な記述になっている。 これは次のように構成されている。

  • pkgs: nixpkgs のルート
  • haskell.packages.ghc921: GHC 9.2.1 用の Haskell 依存関係の名前空間
  • ghcWithPackages: 依存ライブラリとGHCを同時に指定するための Nix 式 (関数)
  • p: [p.ansi-terminal]: 依存ライブラリのリストを返す無名関数 リストはスペース区切り

GHCバージョンにこだわりがない場合 haskell.packages.ghc921 を haskellPackages で置き換えてもいい。

選択可能なGHCバージョンは次のコマンドでわかる。

> nix-env -f '<nixpkgs>' -qaP -A "pkgs.haskell.compiler"
pkgs.haskell.compiler.ghc8107                 ghc-8.10.7
pkgs.haskell.compiler.ghc884                  ghc-8.8.4
pkgs.haskell.compiler.ghc902                  ghc-9.0.2
pkgs.haskell.compiler.ghc921                  ghc-9.2.1
...

また channel で管理されている依存ライブラリのバージョンは次のコマンドでわかる。

> nix-env -f '<nixpkgs>' -qaP -A "pkgs.haskell.packages.ghc921.ansi-terminal"
pkgs.haskell.packages.ghc921.ansi-terminal  ansi-terminal-0.11.1

ghcWithPackages の実装は ここ にある。 GHCとライブラリを別々に指定してしまうとGHCがライブラリを見つけられないらしい。

ちゃんとした開発

haskell-language-server の導入

haskell-language-server はエディタが見るものだし、 複数のGHCバージョンに対応するものでもあるので、 他に使いそうなものと一緒にグローバルにインストールしてしまう。

次のファイルを $HOME/nix/haskell/default.nix にでも用意して nix-env -i をたたくとグローバルにインストールされる。 (この default.nix は こちら を参考にさせてもらっている)。

> nix-env -i --file $HOME/nix/haskell/
...
building '/nix/store/knnnspc3dxxbk9nwv9rqznrrf01rrl8d-user-environment.drv'...

overlay によって haskell-language-server にオプションを与えている。 supportedGhcVersions は読んでの通り。

dynamic=true については何のドキュメントも見つけられないが Template Haskell のコードを扱うなら必要なようだ。 ただしこのオプションを有効にすると haskell-language-server のインストールに余計に時間がかかるかもしれない。 オプションの実装は ここ にある。 haskell-language-server 側のドキュメントでは ここ に相当する処理をしていると思われる。

パッケージの設定

Nix での Haskell 開発について調べていると cabal2nix という名前がよく出てくるが、これを直接使う機会はあまりなさそうだ。 今は nixpkgs の Haskell module が cabal2nix をラップした便利関数をいろいろ提供していて、 それらを使うのが主流のようだ。 この記事でも cabal2nix を直接は使わない方法を説明する。

まずはいつものように cabal init でパッケージを初期化する。 (--enable-nix オプションは不要かもしれない)。

> mkdir test-package
> cd test-package
> nix-shell -p "pkgs.cabal-install" -p "pkgs.haskell.compiler.ghc921" --run "cabal --enable-nix init --exe"

初期化ができたら次のような default.nix を用意する。

これで次のようにすればパッケージの executable が実行できる。 (nix-build は初回だけでいいはず)。

> nix-build
> nix-shell --run "cabal run"
"Hello, Haskell!"

あとは普通に .cabal ファイルを編集して開発していけばいい。

さて、この default.nix は nixpkgs の Haskell module が提供する developPackage を呼んで Haskell パッケージの開発のための設定を構築している。

source-overrides で依存関係のオーバーライドができる。 Hackage のバージョンを指定する方法と tarball のURLを指定する方法がある。

modifier では cabal のいくつかのオプションを設定できる。 設定項目の内訳は ここ にある。

なお、ghcBuild 引数に値を与えて別のGHCバージョンを指定することもできる。 (GHCと base のバージョンが合わないときは version-history を参照して調整する)。

> nix-shell --argstr "ghcBuild" "ghc902" --run "cabal run"

高度な依存関係オーバーライド

この項目を書くときに力尽きた。 依存関係オーバーライドはそれほど困難な道だった。 どうしてもオーバーライドしたいなら次の記事が参考になる。

Nix recipes for Haskellers – Sridhar Ratnakumar

How to override dependency versions when building a Haskell project with Nix

トラブルシューティング

nix-shell の立ち上がりが遅い

direnv Nix integration を導入するといいかもしれない。 direnv 自体は Nix と関係なく使えるひとつのツールだが、 Nix がこれと連携するのにはいくつかのオプションがある。

https://github.com/direnv/direnv/wiki/Nix

自分はシンプルそうな nix-direnv を使っている。

VSCode に direnv 拡張を導入すると VSCode からも direnv の環境が見えるようになる。 ただ過渡期にあるようで何を選ぶか注意が必要そうだ。

cabal run で cabal 自体の出力を無視したい

これは Nix というより cabal の Tips だが次のようにするといいかもしれない。

> nix-shell --run "cabal build && \$(cabal list-bin test-package) | clip.exe"

Mac で nix-env -i が失敗する

すでに修正されたようだが Mac 環境で nix-env -i が失敗することがある。 そのときは以下の issue にある方法で回避できることがある。

https://github.com/NixOS/nixpkgs/issues/163374

Mac で channel が消滅する

理由は分からないが Mac で channel 設定が消滅することがある (nix-channel --list が何も返さない)。 nixpkgs を自分で再設定する必要があるかもしれない。

> nix-channel --add https://channels.nixos.org/nixpkgs-unstable nixpkgs 
> nix-channel --update nixpkgs

家から出ない生活4週間

出社非推奨になったのを機に家から出ない生活を始めて4週間が経った。今までやったことをまとめていく。

食料品

せっかくだから生活で縛りプレイをしてみようと思って Amazon と Yodobashi を禁止にしている。この2社は最近評判が良くないこともあるし。

業務スーパーの通販

いわゆるネットスーパーはサイトが激重になっていたり配達可能日がすべて☓になっていたりで機能不全を起こしているようだった。そこで飲食店がメインターゲットであろう業務スーパーなら今空いているだろうという読み。ここではピザ材料、冷凍野菜、割り箸などを入手している。

とにかく業務スーパーの通販を最大限利用するには冷凍室の容積が必要で、製氷皿とズブロッカには戸棚待機をお願いすることとなった。

日清オールインパスタ

栄養が偏って脚気とか壊血病になるのが怖いのでやや高いしかさばるが栄養補助の目的で食べている。

森永製菓の通販

もともと健康食品用のサイトだったのか矢鱈とコラーゲンを推してくるが、普通のお菓子もいくらか買える。inゼリー、プロテインバー、ラムネなど。

コーヒー

マクドナルドのコーヒーを入手できなくなって家のコーヒー豆消費が増えた。そしたら実家が秋田の『薫』と『アラジン』の豆を送ってくれた。ありがたい。

通販だとだいたい『ネルソンコーヒーロースター』か『たまこのコーヒー』を利用している。

ネルソンコーヒーロースター

仙台所在のロースター。同市の『ネルソンコーヒー』とは別らしい。まじかよ。よくふざけた名前のパックを出している。500g1300円のパックが安い。

たまこのコーヒー

『多摩ワールドコーヒー』の通販。キャラクターがかわいい。vtuber にもなっている。

リモートワークあるいはWFM

椅子

ずっと家にいると腰がやばそうなので椅子は買うことにした。ところが部屋の片付けをしないうちに届いてしまったため倉庫番ゲームが発生し結局腰は痛めた。

『座面と背面が細かいメッシュ』という条件だけで10万円超えるんだよね……。会社の補助制度っぽいものを利用できたとはいえ試座してから決めたかった。

テレビ会議

カメラはラップトップ備え付けのものでいいとしてもマイクが雑音源と近すぎて憚られたため、マイクは買うことにした。が、すでに低価格帯のマイクの在庫はなくなっていたのだった。

BGM

あるていどの騒がしさがないと作業に集中できない人は vtuber の歌枠でも流しておけばいいんじゃないかな。雑に最近のをまとめた再生リストを置いておく。 https://www.youtube.com/playlist?list=PL1bkZdoXAsdpO3zlsyU5zDUdqh8fS2jmG

運動

品薄になる前にリングフィットアドベンチャー買えた勢。

余暇

FF14 と STEAM と vtuber があるので暇はない。

未解決問題と所感

散髪に行けないのが辛い。さいわいなぜかパナソニックの長めに切れるバリカンみたいなの(これ)を持っているので自分で切れないこともない。

窓から遠くが見える立地でないので近眼がさらに進みそう。HMDをかぶると視力が良くなる説を検証してみるべきか。今HMD手に入るのか知らないが……。

あとは4週間も在宅勤務できるなら今までも毎日出社する必要はなかったのではと思う。たとえ出社非推奨が解かれたとしても前のように毎日全員が出社する前提には戻らないんじゃないだろうか。個人的にも満員電車がもたらす不快指数に加担するのは心苦しい。

とは言え

とは言えそろそろ外に出たくもなってきた。記憶が確かなら歩いていける距離に卵の自販機があったはずだ。さしあたってそこを目標地点にして人のいない時間帯に出歩いてみることにしよう。

HaskellやっていくGHC8.8.1令和元年白露の候

GHC 8.8.1 が出たので雑に環境を整えていきます。

OS: Ubuntu 18.04 LTS on VirtualBox on Windows

f:id:kazeula:20190914233248p:plain
screenshot

GHC, Cabal

最近は ghcup で入れている。

$ ghcup upgrade
$ ghcup list --tool all
$ ghcup install 8.8
$ ghcup install-cabal latest

~/.ghcup/bin と ~/.cabal/bin にPATHが通っていない場合は通しておく。

stack は手になじまない感じがして最近使っていない。 stack のいいところとしてスクリプトが書けるというのがあったが、最近は cabal でもいい感じに shebang を書けばスクリプティングできるようになったというのもある。

Haskellツール

使いそうなHaskellツールをインストールする。

$ cabal new-update
$ cabal new-install doctest hlint ghcid fast-tags --installdir=$HOME/.cabal/bin

まだ GHC 8.8.1 が世に出て日が浅いのでインストールできないパッケージも多い。 自分が試した時点では以下のものはインストールできなかった。

  • apply-refact
  • hasktags
  • stylish-haskell
  • hhp (HEADでは対応済みだがhackageに上がっていない?)
  • haskell-ide-engine (rc版は出ている?)

ツールによってはわざわざGHC8.8.1でインストールする必要がないものもある。 そういうものは ghcup set でGHCを切り替えてインストールするといいかもしれない(試してはいない)。

エディタ

モーダルでカスタマイザブルなエディタがいいので Spacemacs を使う。 おそらく Emacs 26 が必要になるが素の apt だと入らないかもしれない。 そのときは apt repository を追加するなどする。 あと Spacemacs は master がだいぶ古くて皆 develop を使っているようなので自分もそうしている。

自分の.spacemacs: https://gist.github.com/syocy/5ea4d65f3f819a2a2e82e3f8ff2f3d82

シェルは fish シェルを使っているが、そのせいか Spacemacs がうまくPATHを継承しないことがあった。 そのときは SPC f e e で環境設定ファイルを開いて直接編集すればいいようだ。

Haskell layer の補完バックエンドは company-ghci を使っている。 dante は自分の環境では動かなかった(戦いの爪痕が.spacemacsに残っている)。 company-ghci は SPC m s b で ghci をロードしておかないと動作しないので注意。

インタラクティブなエラーチェックは Haskell layer に含まれる flycheck-haskell を使っている。 ghci のほか、hlint があるとそれも走らせてくれる。 タイル型ウィンドウマネージャ(i3wm)を使っていた頃は ghcid を使っていた。 flycheck-haskell を使っていくうちに不都合があれば戻るかもしれない。

そのほか細かい設定は .spacemacs を見てほしい。

サンプルプロジェクト

cabal init が最近強化されたらしく、プロジェクトを作るのがより楽になったようだ。 cabal init --help を見るとオプションが増えている。 たとえばテストのあるライブラリとしてプロジェクトを作るには以下のようにする。

$ cabal init --lib --source-dir=src --tests --test-dir=test -p sample-project

ちなみに冒頭の画像を再現するには、

  • SPC e L で flycheck のエラーウィンドウを開く
  • SPC f t (SPC p t) で treemacs ウィンドウを開く
  • SPC w でウィンドウをお好みで配置する

などすればできる。 なおウィンドウレイアウトの保存は SPC l でできる(昔の eyebrowse layer?)。

以上、GHC 8.8.1 でも Haskell やっていくぞ。

追記: Cabal 3.0 から一部オプションのデフォルト値が変わった関係で flycheck-haskell や doctest のようなツールが動作しないということがあった。そういうときは cabal のオプションに --write-ghc-environment-files=ghc8.4.4+ を指定するとよい。 stackoverflow にもその旨を書いた。