新しいディスプレー

f:id:kakkun61:20200307011401j:plain

会社が 4K ディスプレーなのもあって、家でプログラミングしてたら画面が窮屈に感じてきてうちも 4K ディスプレーを導入しました。

特に Elm の標準のフォーマットが密度低いのでフル HD だと不便だったのが最後の引き金でした。

4K かつ HDR にしました。

KEIAN 28インチ4K対応LEDモニター KWIN28 ゲーミングモニター

KEIAN 28インチ4K対応LEDモニター KWIN28 ゲーミングモニター

  • 発売日: 2019/04/20
  • メディア: Personal Computers

4K で High DPI 設定による高精細は言わずもがな HDR も綺麗ですね。

綺麗なんだけど WindowsHDR 出力をオンにしたときの SDR(今までのダイナミックレンジを表すレトロニム)コンテンツが白くかすんでしまって WindowsHDR オンにするのは時期尚早な感じがします。

自分は Windows では HDR はオフにしました。

PS4 をせっかく Pro 買ったのに今まで full HD の SDR でしか出力してなくもったいなかったのがやっともったいなくなくなり、Windows がいまいちでもとりあえず満足です。

HDMI にバージョンがあることを知らず PS4 Pro から 4K 60Hz が出ず後悔しそうでしたが、ディスプレーの HDMI 入力端子によってバージョン 1.4 と 2.0 が違うらしく挿し直したらちゃんと出力してくれました よかった。

ディスプレー買って初めて輝点があったのでちょっと凹んだんですが、グレー一色表示とかしなければ意外とめだたなくてよかったです。 でもやっぱり今度から輝点黒点保証付けて買おうかな。

……

…………

と、これだけでは終わらず右下のディスプレーも新しく買ってて、こっちはフル HD だけど Adobe RGB カバー率 99% のカラーキャリブレーションディスプレーです。

10bit カラーでもあります。

デジカメで Adobe RGB で撮ってましたがこれまで sRGB でしか見れてなく、見れないものを画像編集しててモヤモヤしてたのが、やっと本当の色を見ることができます。

特に緑の鮮やかさに差が大きいらしいのでわざとそういうの撮って比較したいですね。

カラーキャリブレーションの機能はこれ単体ではできなくカラーセンサーがまた高いのだけどそれは先の話ということで……

4K かつ Adobe RGB にすればよかったじゃんという意見はごもっともなんだけど14万円とかして用途別に2台買う方がやすいんですよね……

postgresql-pure を開発しました

この記事は Haskell Advent Calendar 2019 の6日目の記事です。


hackage.haskell.org

postgresql-pure は HaskellPostgreSQL ドライバー(クライアントライブラリー)で次のような目標で開発しました。

  • マルチコア環境でのパフォーマンス向上
    • 暗黙のロックを回避する
  • マルチプラットフォーム対応
    • C ライブラリーの libpq への依存をなくして特に Windows でのビルドを容易にする
    • 既存ライブラリーとしては postgres-wire が高速だがそれは Windows をサポートしていない
    • pure Haskell 実装のため Eta などの環境へも移植しやすい可能性がある

使用方法

簡単に使用方法を説明します。

下記のようなテーブルがあるとします。

CREATE TABLE person (
  id serial PRIMARY KEY,
  name varchar(255) NOT NULL
);
INSERT INTO person (name) VALUES ('Ada');

このとき ghci で下記のように実行できます。

> :set -XOverloadedStrings
> :set -XFlexibleContexts
> :set -XDataKinds
> :set -XTypeFamilies
> :set -XTypeApplications
> 
> import Database.PostgreSQL.Pure
> import Data.Default.Class (def)
> import Data.Int (Int32)
> import Data.ByteString (ByteString)
> import Data.Tuple.Only (Only (Only))
> import Data.Tuple.List.Only ()
> import Data.Tuple.Homotuple.Only ()
> 
> conn <- connect def
> preparedStatementProcedure = parse "" "SELECT id, name FROM person WHERE id = $1" Nothing
> portalProcedure <- bind @_ @2 @_ @_ "" BinaryFormat BinaryFormat (parameters conn) (const $ fail "") (Only (1 :: Int32)) preparedStatementProcedure
> executedProcedure = execute @_ @_ @(Int32, ByteString) 0 (const $ fail "") portalProcedure
> ((_, _, e, _), _) <- sync conn executedProcedure
> records e
[(1,"Ada")]

重要な部分を抽出すると parsebindexecute の順に呼びだし、最後に sync でサーバーに送信します。parsebindexecute は入出力のない関数であり、リクエストのビルダーと対応するレスポンスのパーサーを構築しています。そしてそのビルダーとパーサーを sync が使用して送受信を行ないます。bindモナド値を返すようになっているのは失敗する可能性があるためで、型は MonadFail m => m a となっており IO a ではありません。

型適用が所々に明記されていますが、これは ghci で実行しているので結果の使用部分からの推論ができないためで、実際のコードではほとんどの場合で型適用の明記は必要なくなります。

postgresql-pure では、タプルによる要素数の不一致を型検査で検出するインターフェース Database.PostgreSQL.Pure(上記の例)と、しないインターフェース Database.PostgreSQL.Pure.List と、HDBC 互換インターフェース Database.HDBC.PostgreSQL.Pure の3つを提供しています。

高速化

高速化に寄与した技術を説明します。

Haskell

まずは Haskell 一般に関連するものについての技術です。

大きな byte string を何度も確保しない

送信と受信のたびに byte string を確保することを避けました。約 3 kB 以上のメモリーを確保すると暗黙のグローバルロックがかかります1。送受信のたびに確保するのをやめ、代わりにバッファとして確保した領域を何度も再利用するようにしました。手動によるメモリー管理のために bytestring パッケージの Data.ByteString.Internal.mallocByteString と network パッケージの Network.Socket.sendBufrecvBuf を使用しました。

mallocByteString :: Int -> IO (ForeignPtr a)
sendBuf :: Socket -> Ptr Word8 -> Int -> IO Int
recvBuf :: Socket -> Ptr Word8 -> Int -> IO Int

接続時に mallocByteString で2つのバッファを確保します。送信するメッセージは Data.ByteString.Builder.Extra.BufferWriter によって構築し、受信するメッセージは Data.Attoparsec.parseWith でパースします。

シンボルには ShortByteString を使用する

LISP のシンボルのような短い文字列には ShortByteString を使用しましょう。ShortByteStringByteString よりもオーバーヘッドが少なく、またヒープフラグメンテーションを引き起こしません。ShortByteString には lengthindex のような簡単な操作だけが提供され複雑な操作は提供されていません。このライブラリーではサーバーのパラメーターを保存するために使用しました。

PostgreSQL

次に PostgreSQL 固有の効率化について説明します。

PostgreSQL プロトコルには2つの問い合わせ方法があります。ひとつは簡易問い合わせで、もうひとつは拡張問い合わせです。簡易問い合わせには最小限の機能しかなくプリペアドステートメントやバイナリーフォーマット、結果を1レコードずつフェッチすることなどはサポートされていないので、このライブラリーでは拡張問い合わせを採用しました。

PostgreSQL プロトコルTCP の上で動作し、複数のメッセージは結合してひとつの TCP ペイロードに格納することができます。

下記の図はメッセージをひとつずつ送信した場合を表しています。

f:id:kakkun61:20191203182127p:plain
メッセージをひとつずつ送信した場合

メッセージを結合した場合は下記のようになります。

f:id:kakkun61:20191203182234p:plain
メッセージを結合した場合

メッセージを結合することで送受信するデータ量を減らし、またシステムコールの回数も減らすことができます。

既存のドライバー

ベンチマーク

下記のような単純な定数値のみの問い合わせを秒間何回問い合わせられるかを計測しました。定数値を使用したのはサーバーがボトルネックにならないためです。

SELECT 2147483647 :: int4, 9223372036854775807 :: int8, 1234567890.0123456789 :: numeric, 0.015625 :: float4, 0.00024414062 :: float8, 'hello' :: varchar, 'hello' :: text, '\xDEADBEEF' :: bytea, '1000-01-01 00:00:00.000001' :: timestamp, '2000-01-01 00:00:00.000001+14:30' :: timestamptz, '0001-01-01' :: date, '23:00:00' :: time, true :: bool;

環境は下記の通りです。

計測結果を下に示します。縦軸は秒間リクエスト数で横軸はスレッド数です。

このライブラリーも postgres-wire も約4スレッドまではほぼ同じパフォーマスです。それ以上になると postgres-wire が線形比例を下回っていくのに対し、このライブラリーはより線形に近くなっています。

(比較対象は開発途中でのベンチマークにおいておそかったものを除去しています。)

f:id:kakkun61:20191203182308p:plain
ベンチマーク結果


postgresql-pure は IIJ イノベーションインスティテュートの業務として作成されました。

GHC 環境構築 概観 と PowerShell

Haskell の開発環境を構築する方法は何通りかあり、新しいツールが出ると「今はこれだ」とほめるブログも公開されますがそれが初学者を混乱させることがよくあります。

本記事ではビルドツールのインストールにしぼり、開発支援、例えば ghc-mod・haskell-ide-engine・hhp には言及しません。

いくつか典型的な構築方法を確認した後、新たに私が作成したツールを紹介しより初学者を混乱に落とし入れます。

重鎮 Haskell Platform

www.haskell.org

Haskell 解説書史1の第1波から第3波までのデファクトスタンダードであった重鎮 Haskell Platform です。コンパイラGHC とパッケージマネージャー Cabal、それにいくつかの準標準ライブラリーをまとめてインストールできるツールでこれをインストールするだけで入門書のコードはすぐ動くというようなものです。

現在では Windows 向けにのみ提供されています。Haskell Platform のページでは macOS 向けには下記で紹介する Stack がすすめられています。Linux 向けには Stack と、同じく下記で紹介する Ghcup またはディストリビューション付属のパッケージマネージャー(apt や yum など)がすすめられています。

救世 Stack

www.haskellstack.org

前世紀、人々は依存地獄に苦しめられていました。その苦しみを取り除くために救世主がごとく現れたのが Stack でした。

依存地獄とは依存ライブラリーのバージョン解決時に遭遇する問題でこれを解決しなければビルドができません。例えば A と B というパッケージに依存していたとしましょう。A はまた C に依存し同じく B は C に依存していたとします。このとき、A が要求する C のバージョンと B が要求する C のバージョンが同じであれば何も問題はありません。問題があるのは同じでない場合です。A または B 自体のバージョンを下げるなどして C のバージョンが合うように調整しないといけません。

OK

  • A
    • depends C-1
  • B
    • depends C-1

Error

  • A-3
    • depends C-2
  • B-1
    • depends C-1

Resolve (e.g.)

  • A-2
    • depends C-1
  • B-1
    • depends C-1

Stack は、主要なライブラリーに関してこの問題が解決されたバージョンのセットとそれをビルドできる GHC バージョンをペアにしたものを resolver として管理し提供しました。

また、ライブラリーや GHC のインストールされる場所をグローバルではなく隔離された場所にすることで、1つのマシンに入っている別々のプロジェクトが別々のバージョンのライブラリーや GHC を利用することを容易にしました。当時の Cabal はそれができませんでした2

これによって、ユーザーは resolver を指定することで依存地獄を見ることなくアプリケーション開発ができ、また複数プロジェクトの開発でわずらわされることがなくなりました。

新生 Cabal

www.haskell.org

Cabal は環境構築ツールではありませんが Stack と比較するという意味でここで紹介します。

Cabal は Haskell 標準のパッケージマネージャーで、Haskell Platform でインストールされますし、Stack も Cabal をラップして使用しています。

Stack が大躍進を遂げたことで、それまでライバルのいなかった Cabal にも変化が求められました。これまで Cabal のサブコマンドだった build などは v1-build とされ、新たに互換性のない v2-build が導入されました。v2-build では Stack の特徴の1つである「隔離してインストールする」機能が搭載されグローバルを汚染することがなくなりました(注:コメントを参照)3。build サブコマンドは v1-build の別名でしたが Cabal 3.0.0.0 でついに v2-build に変更されました。

この大幅改善によって一時は Stack に移行していたユーザーの一部は直接 Cabal を使うように戻ったようです。

甲殻 Ghcup

www.haskell.org

世の中では Haskell 以外の言語も生まれ進化しています。その内の1つに Rust があります。Rust はコンパイラー rustc とパッケージマネージャー Cargo の管理のために Rustup を開発しました。

Rustup は指定したバージョンの rustc と cargo をインストールする機能と、rustc コマンドで実際に呼び出されるコンパイラーを切り替える機能のあるものです。これと同じものを GHC も導入し ghcup としました。これによって複数の GHC をインストールしそれを切り替えて使用することが簡単にできるようになりました。

Ghcup と Cabal の v2 をインストールし、Stack が提供するライブラリーバージョンのセットを Cabal から使うとほぼ Stack がやっていたことができるようになります。

拙作 Ghcups

www.powershellgallery.com

私にとって Ghcup には1つ問題があり、それは Ghcup が sh 製であることです。私は Windows ユーザーで PowerShell を常用しています。これまでは Stack を使ってきていましたが、GHC を切り替える機能が欲しくなり同様の機能を持つ Ghcups を作りました。Ghcup と PowerShell(PS)のかばん語です。

実装の話をすると、Rustup や Ghcup は rustc や ghcシンボリックリンクとして実際の実行ファイルを指すようにしていますが、Windows ではシンボリックリンクの生成は管理者権限を必要とするため使いたくありません4。Ghcups では環境変数 Path を編集することで実現することにしました。副効果として、その Path の編集は1つの PowerShell プロセスに閉じるため複数の PowerShell があるときに同時にそれぞれ別々のバージョンを指定することができるようになりました。欠点としては PowerShell を経由しない GUI アプリケーションに対しては影響を及ぼせません。

GHC と Cabal のインストールは現状 Chocolatey に依存しています。これは実装を省いてとりあえず動くものが欲しかったのでこうなっています。要求があれば非依存にするかもしれません。

結局どれを選べばよいのか

私の私感ですが次のように選ぶのがよいかと思います。

  • 入門書を読んだのでとりあえずサンプルを試したい
    • Haskell Platform・Stack・Ghcup どれでもよいでしょう
    • 複数の GHC を試したくなったときは Haskell Platform はおすすめしません
    • Stack の場合はコマンドを読み替える必要があります(例:ghcstack ghc --
  • アプリケーションを作りたい
    • Stack がおすすめです
    • 特に Yesod のような依存の多いものは依存地獄が起きる可能性が高いのでより Stack をすすめます
    • 依存に問題がないなら他でもよいです
  • ライブラリーを作りたい
    • Ghcup + Cabal をおすすめします
    • 複数の GHC バージョンで動作確認したりするのに Ghcup は適しています
    • Stack でも stack.yaml をバージョンごとに複数用意するなどして利用することはできます

Ghcups で環境構築する

Ghcups を使って環境構築し切り替えをするところまで説明します。

実行ポリシーを設定して管理者権限で PowerShell を起動します5

powershell -ExecutionPolicy Bypass

PowerShell Gallery に公開しているので Install-Module でインストールできます。

> Install-Module ghcups

インストールが終わると同じく管理者の PowerShell にて下記のように Chocolatey をインストールします。ユーザーの PowerShell で実行した場合は自動的に管理者権限の PowerShell が起動するようになっているのでユーザー権限でも構いません。

> Import-Module ghcups
> Install-Choco
Getting latest version of the Chocolatey package for download.
Getting Chocolatey from https://chocolatey.org/api/v2/package/chocolatey/0.10.15.
Downloading 7-Zip commandline tool prior to extraction.
Extracting C:\Users\KAZUKI~1\AppData\Local\Temp\chocolatey\chocInstall\chocolatey.zip to C:\Users\KAZUKI~1\AppData\Local\Temp\chocolatey\chocInstall...
Installing chocolatey on this machine
We are setting up the Chocolatey package repository.
The packages themselves go to 'D:\chocolatey\lib'
  (i.e. D:\chocolatey\lib\yourPackageName).
A shim file for the command line goes to 'D:\chocolatey\bin'
  and points to an executable in 'D:\chocolatey\lib\yourPackageName'.

Creating Chocolatey folders if they do not already exist.

Chocolatey (choco.exe) is now ready.
You can call choco from anywhere, command line or powershell by typing choco.
Run choco /? for a list of functions.
You may need to shut down and restart powershell and/or consoles
 first prior to using choco.
Ensuring chocolatey commands are on the path
Ensuring chocolatey.nupkg is in the lib folder

GHC と Cabal のインストールとその切り替えは下記のようにします。ユーザーの PowerShell にて下記のようにセットアップします。管理者権限でないことの確認をされますがそのまま実行して大丈夫ですので y を選択してください6

> Import-Module ghcups
> Install-Ghc 8.8.1
Chocolatey v0.10.15
Chocolatey detected you are not running from an elevated command shell
 (cmd/powershell).

 You may experience errors - many functions/packages
 require admin rights. Only advanced users should run choco w/out an
 elevated shell. When you open the command shell, you should ensure
 that you do so with "Run as Administrator" selected. If you are
 attempting to use Chocolatey in a non-administrator setting, you
 must select a different location other than the default install
 location. See
 https://chocolatey.org/install#non-administrative-install for details.


 Do you want to continue?([Y]es/[N]o): y

Installing the following packages:
ghc
By installing you accept licenses for the packages.
Progress: Downloading ghc 8.8.1... 100%

ghc v8.8.1 [Approved]
ghc package files install completed. Performing other installation steps.
The package ghc wants to run 'chocolateyInstall.ps1'.
Note: If you don't run this script, the installation will fail.
Note: To confirm automatically next time, use '-y' or consider:
choco feature enable -n allowGlobalConfirmation
Do you want to run the script?([Y]es/[A]ll - yes to all/[N]o/[P]rint): a

Downloading ghc 64 bit
  from 'https://downloads.haskell.org/~ghc/8.8.1/ghc-8.8.1-x86_64-unknown-mingw32.tar.xz'
Progress: 100% - Completed download of D:\chocolatey\lib\ghc.8.8.1\tmp\ghcInstall (377.31 MB).
Download of ghcInstall (377.31 MB) completed.
Hashes match.
D:\chocolatey\lib\ghc.8.8.1\tmp\ghcInstall
Extracting D:\chocolatey\lib\ghc.8.8.1\tmp\ghcInstall to D:\chocolatey\lib\ghc.8.8.1\tools...
D:\chocolatey\lib\ghc.8.8.1\tools
Extracting D:\chocolatey\lib\ghc.8.8.1\tools\ghcInstall~ to D:\chocolatey\lib\ghc.8.8.1\tools...
D:\chocolatey\lib\ghc.8.8.1\tools
PATH environment variable does not have D:\chocolatey\lib\ghc.8.8.1\tools\ghc-8.8.1\bin in it. Adding...
Hiding shims for 'D:\chocolatey\lib\ghc.8.8.1\tools'.
Environment Vars (like PATH) have changed. Close/reopen your shell to
 see the changes (or in powershell/cmd.exe just type `refreshenv`).
 The install of ghc was successful.
  Software installed to 'D:\chocolatey\lib\ghc.8.8.1\tools'

Chocolatey installed 1/1 packages.
 See the log for details (D:\chocolatey\logs\chocolatey.log).
> Install-Ghc 8.6.5
(省略)
> Install-Cabal 3.0.0.0
(省略)
> Set-Ghc 8.8.1
> ghc --version
The Glorious Glasgow Haskell Compilation System, version 8.8.1
> Set-Ghc 8.6.5
> ghc --version
The Glorious Glasgow Haskell Compilation System, version 8.6.5

Haskell ユーザーで PowerShell ユーザーの方がいればぜひ試してみてください7


  1. Haskell 解説本 小史 - golden-luckyの日記

  2. cabal-dev を使えばできた。

  3. v2 は常用していないのでまちがっていればツッコミがほしい。

  4. 追記:ジャンクションなら管理者権限は必要なかった。

  5. “Chocolatey detected you are not running from an elevated command shell (cmd/powershell).” の確認をしないオプションをご存じの方いれば教えてください。“Do you want to run the script?” の確認はしたいです。

  6. Set-ExecutionPolicy を使用してグローバルに制限を緩める方法がよく紹介されますがセキュリティの観点からこちらの方をすすめます。ショートカットをオプション付きで設定して作成するとよいです。

  7. 何人いるんだろう。

C ドライブ依存を減らす

いろいろなソフトウェアがデフォルトで何でも C ドライブを使う。

C ドライブが手狭になってきたのでいくらかのデータを別ドライブに移行する方法をメモしておく。

Stack

Stack(Haskell)のリソースはユーザー環境変数 STACK_ROOT の指すディレクトリーに保存される。デフォルト値は C:\sr である。パス長制限の問題があるので浅い場所がよい。D:\stack に変更した。

Stack によってインストールされる GHC や MSYS2 は LOCALAPPDATA\Programs\stack にある。これは STACK_ROOT\config.yamllocal-programs-path を記載すると変更できる。非 Windows 環境では STACK_ROOT\programs らしいということもあり D:\stack\programs に変更した1

STACK_ROOT\config.yaml に記載する Non-project-specific configSTACK_ROOT\global-project\stack.yaml に記載する Project-specific config は別物なので注意する。

75 GB ぐらいだった。

Chocolatey

Chocolatey のリソースはシステム環境変数 ChocolateyInstall の指すディレクトリーに保存される。D:\Chocolatey に変更した。choco.exeChocolateyInstall\bin にあるのでシステム環境変数Path に追加しておく2

いくつかのパッケージの実行ファイルが保存される場所はユーザー環境変数 ChocolateyToolsLocation によって示される。D:\tools に変更した。


  1. LOCALAPPDATAディレクトリーの役割についてはWindowsのディレクトリ構成ガイドライン - torutkのブログが日本語で完結に知れる。

  2. 通常ユーザー環境変数は同名のシステム環境変数を上書きするが、Path は特別で実行ファイルを探すときはシステム環境変数Path から検索した後にユーザー環境変数Path から検索する。

Zenza Bronica S 系用接写リング

Zenza Bronica S 系用接写リングの説明書(をスキャンしたものをプリントしたもの)を入手したので文字データに起こした。

Zenza Bronica S 系接写リング

docs.google.com

この接写リングは4個(C-A C-B C-C C-D)で1組になっています 使用できるレンズも 40 mm~200 mm までと巾広く 標準レンズ使用の場合で等倍までの近接撮影ができます 4個の接写リングは撮影の目的に合わせ ご自由に組合せてご使用ください 別表は S2 型・C2 型カメラに 接写リングと交換レンズを組合せた場合の撮影倍率と露出倍数の関係を示します

(表)

● 被写体に近接するに従って フィルム面とレンズの距離は遠くなり 実際のレンズの明るさ(F値)は減少します 表中の露出倍数を参考にして露出時間を増加してください

● 接写には ブロニカ TTL 露光計の併用をおすすめいたします 露出倍率などに関係なく 指針を 0 に合わせるだけで 正しい露光が得られ 完全に遮光されたピントフードがついていますので映像は明瞭です

最近の自分の Haskell 開発環境(Windows)

id:syocy のブログを見たので Windows で自分がどうしているかをメモしておく。

syocy.hatenablog.com

Stack

最近は ghcup があるがシェルスクリプト製で自分は PowerShell ユーザーなので stack を使っている。(ghcup はなんで Haskell 製じゃないんだ1?)

GHC 8.8 を使うには resolver は ghc-8.8 や nightly を指定する。まだ LTS にはなっていない。

エディター

エディターは Spacemacs を使っている。前は IntelliJ IDEA に HaskForce プラグインを入れて使っていたが、ソースコード量に対して線形以上に動作が遅くなっている感覚があって使わなくなってしまった。

Spacemacs の提供してる Haskell レイヤーに Happy Haskell Programming を追加して使っている。

.spacemacs.d\init.el に次のように追記している(リポジトリー)。

(defun dotspacemacs/user-config ()(add-to-list 'load-path "~/.spacemacs.d/hhp/elisp")
  (autoload 'hhp-init "hhp" nil t)
  (autoload 'hhp-debug "hhp" nil t)
  (add-hook 'haskell-mode-hook (lambda () (hhp-init))))

HHP は stack プロジェクトを考慮しないので、unveil-stack を使って、ghc コマンドが呼ばれると現在のプロジェクトの resolver が指定する ghc を呼び直すようにしている。hhpc や hhpi についても同様で stack exec -- hhpc のように呼び直している。

Spacemacs の Haskell レイヤーについてはここを、HHP の機能についてはここを参照のこと。

Makefile

Haskell プロジェクトのビルドは stack 経由だが、その他のタスク用に PowerShell 版の Make のような Psake を使っている(発音は「酒」と同じだそうだ)。

リポジトリーの全ファイルに stylish-haskell や hlint をかけたり、改行を CR/LF から LF に変換したりするのに使っている。

Task Format {
    Exec { Get-ChildItem -Filter '*.hs' -Recurse src | ForEach-Object { stack exec -- stylish-haskell -i $_.FullName } }
}

Task Lint {
    Exec { stack exec -- hlint src }
}