[入門] HaskellでGUIプログラミング 導入編

HaskellでGUIプログラムを作成する方法を解説します。
具体的には[gi-gtk-declarative](https://hackage.haskell.org/package/gi-gtk-declarative)というライブラリを使ってHello Worldするまでの流れを解説します。開発環境はWindows 10です。

<!--
分量の都合上、[stack](https://docs.haskellstack.org/en/stable/README/)の使い方、Haskellの基本事項(特に[モナド変換子](http://hackage.haskell.org/package/transformers-0.5.6.2/docs/Control-Monad-Trans-Class.html))、[pipesライブラリ](https://hackage.haskell.org/package/pipes)の使い方の説明は省きます。
-->

ソースコードの詳しい解説は次の記事でする予定です。

## Gtk+3開発環境の構築
https://www.msys2.org/ からMSYS2のインストーラーをダウンロードして実行します。バージョンが古いかもしれませんが、この[リンク](https://repo.msys2.org/distrib/x86_64/msys2-x86_64-20200903.exe)をクリックしてもインストールをダウンロードできます。
![2020-10-21-22-05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/126131/7b81851e-7aff-f287-ff54-3b44a38dd905.png)

次にMSYS2を実行してMSYSの端末を開き、必要なパッケージをインストールします。

```shell
pacman -Syy
pacman -S -q --noconfirm mingw-w64-x86_64-glade mingw64/mingw-w64-x86_64-pkg-config mingw64/mingw-w64-x86_64-gobject-introspection mingw64/mingw-w64-x86_64-gtksourceview3
```

最後に環境変数の設定を行います。

```
PATH: <msys>, <msys>/mingw64/bin, <msys>/usr/bin
PKG_CONFIG_PATH: <msys>\mingw64\lib\pkgconfig
SET XDG_DATA_DIRS: <msys>\mingw64\share
```

環境変数が存在しない場合は新規作成、存在する場合は追記してください。また、`<msys>`は先ほどMSYS2をインストールしたフォルダに置き換えてください。

## stackのインストールと設定
https://docs.haskellstack.org/en/stable/README/ からインストーラーをダウンロードして実行します。この[リンク](https://get.haskellstack.org/stable/windows-x86_64-installer.exe)をクリックしてもインストーラーをダウンロードすることができます。
![2020-10-21-21-30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/126131/14b2c10f-6bef-08be-3959-24293bef6846.png)

インストールが終わったら、端末([Windows Terminal](https://www.microsoft.com/ja-jp/p/windows-terminal/9n0dx20hk701?activetab=pivot:overviewtab)など)を立ち上げ、

```shell
cd <フォルダ>
stack new <プロジェクト名>
```

を実行してstackプロジェクトを作ります。このコマンドを実行すると、`<フォルダ>`下に`<プロジェクト名>`という名前のフォルダが作られます。このフォルダがプロジェクトのルートフォルダとなります。

次にエディタを開き、ルートフォルダ下の`stack.yaml`というファイルを編集します。

```yaml:stack.yaml
resolver: lts-16.18
skip-msys: true
allow-newer: true
extra-deps:
- haskell-gi-0.24.5
- haskell-gi-base-0.24.3
- gi-harfbuzz-0.0.3
- gi-pango-1.0.23
- gi-gtk-declarative-0.6.3
- gi-gtk-declarative-app-simple-0.6.3
extra-lib-dirs: 
- <msys>\mingw64\bin
ghc-options:
  "$locals": "-threaded"
```
`<msys>`は先ほどMSYS2のインストールフォルダに置き換えてください。

 さらに、`package.yaml`というファイルも編集します。dependenciesという項目を以下に書き換えてください。

```yaml:package.yaml
dependencies:
- base >= 4.7 && < 5
- haskell-gi-base
- gi-gtk
- gi-gtk-declarative
- gi-gtk-declarative-app-simple
- pipes
- pipes-extras
```

GHC(Haskellのコンパイラ)をインストールします。

```
stack setup
```

最後に、[ライブラリのバグ](https://github.com/commercialhaskell/stack/issues/3154)を回避するためにライブラリの差し替えを行います。

まずはstackがGHCをどこにインストールしたのかを調べます。

```
stack path --programs
```

そして、ライブラリの差し替えを行います。

```
cp <msys>/mingw64/bin/zlib1.dll <ghc>/ghc-<version>/mingw/bin/zlib1.dll
```

`<msys>`はMSYS2のインストールフォルダに、`<ghc>`は先ほど調べたGHCのインストール場所に、`<version>`はインストールしたGHCのバージョンに置き換えてください。

## ソースコードの記述

プロジェクトフォルダには既にMain.hsというファイルがあると思います。このファイルの中のmain関数がプロジェクトのエントリポイントとなります。

そのため、まずはMain.hsを書き換えます。

```haskell:Main.hs
{-# LANGUAGE OverloadedStrings, OverloadedLists, OverloadedLabels #-}
module Main where

import Data.Function ( (&) )
import Data.Text ( Text )
import Pipes
import qualified Pipes.Extras as Pipes
import Control.Monad ( void )

import GI.Gtk ( Label(..), Window(..) )
import GI.Gtk.Declarative
import GI.Gtk.Declarative.App.Simple

data State = Initial | Showing Text
data Event = Show Text | Closed

view' :: State -> AppView Window Event
view' s =
    bin
        Window
        [ #title := "First Example"
        , on #deleteEvent (const (True, Closed))
        , #widthRequest := 400
        , #heightRequest := 300
        ]
    $ case s of
        Initial -> widget Label [ #label := "No message" ]
        Showing msg -> widget Label [ #label := msg ]

update' :: State -> Event -> Transition State Event
update' _ (Show msg) = Transition (Showing msg) (return Nothing)
update' _ Closed = Exit

main :: IO ()
main = void $ run App { view = view'
                      , update = update'
                      , inputs = [ greetings ]
                      , initialState = Initial
                      }
    where
        greetings =
            cycle ["Hello", "World"]
                & map Show
                & Pipes.each
                & (>-> Pipes.delay 1.0)
```

元々のMain.hsでは、srcフォルダのLib.hsファイルをインポートして使っていました。Main.hs書き換えたことでこのファイルは要らなくなったので削除してください。

仕上げにプロジェクトをコンパイルして実行します。

```shell
stack build
stack exec <プロジェクト名>-exe
```

![2020-10-21-23-07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/126131/a6c4a0ae-85a8-0ecb-b469-255c5bd8595b.png)

## 参考サイト
[Using haskell-gi in Windows](https://github.com/haskell-gi/haskell-gi/wiki/Using-haskell-gi-in-Windows)
https://github.com/owickstrom/gi-gtk-declarative/blob/master/examples/Hello.hs