代数プログラミング入門 Ch2 環境構築

資料
Published on 2024-10-18 under the tag algebra, lecture, statistics, haskell

Table of Contents

1 Haskellセットアップ

言語の特徴や意味を色々と説明してきましたが,習うより慣れろということで,そろそろHaskellを利用してみましょう.Haskellの開発環境には様々なものがありますが,現在良く使われているものとしてCabal + GHCupあるいはStackの2つがあります. CabalとStackはプロジェクトのビルドを行うためのアーキテクチャであり,GHCupは周辺環境のインストーラーです. どちらで開発を行ってもいいのですが,本稿ではStackを用います.

Stackは現在のHaskellの標準的なコンパイラである,Glasgow Haskell Compiler(GHC)に基づいたビルド環境です(cabalもGHCですが). 他の言語と同様にHaskellでも様々なpackage(ライブラリ)を利用するのですが,package毎に他のpackageや,GHC(Haskellのコンパイラ)との依存関係があります.それらを使用するpackage事に調整することが人間には至難の業であり, 特定のpackageの依存関係を満たせば他のpackageの依存関係が満たされなくなるという試行錯誤を永遠と繰り返すことをcabal hellなどと呼びます.

Stackにはそのようなpackage間の依存関係を満たすバージョンの組み合わせ(resolver)を利用して,自動で解決してくれる機能があり,Haskellでのブロジェクトの開発を容易にしてくれます. resolverの集まりをStackageといい, resolverで扱われるpackageをまとめて管理するレポジトリのことをHackageといいます.

1.1 環境構築

Stackの環境構築の方法は基本的には,公式サイトに従ってください. 使用しているOS毎にインストール方法が異なるので注意しましょう特にMacユーザーはIntel Mac と Apple silliconでインストール方法が異なるので正しい方を選択するようにしてください.

インストールが終わったら,以下のコマンドでstackを最新版にupgradeします.

stack upgrade

次に,開発用のディレクトリに移動して,開発用のプロジェクトを作成していきます. Stackでは,新しいプロジェクトの作成はstack new [project-name] コマンドで行われます. stack new [project-name]コマンドで新しいプロジェクトを作成すると,必要なファイルが含まれた[project-name]という名前のディレクトリが作成されます. 作成されたディレクトリに移動しましょう.

> ls

> stack new hello-world
> ls
hello-world

> cd hello-world

作成されたディレクトリの構成は以下のようになっています.

 tree
.
├── CHANGELOG.md
├── LICENSE
├── README.md
├── Setup.hs
├── app
│   ├── hello.hs
├── hello-world.cabal
├── package.yaml
├── src
│   └── Lib.hs
├── stack.yaml
└── test
    └── Spec.hs

それぞれの用途と意味は以下のとおりです.




これらの利用法は,今後ライブラリを使用し始めたときに改めて学習すれば大丈夫なので,取り敢えずプログラムを作成してきましょう.

1.2 Hello World

環境構築が上手くできているかを確認するために,Hello World用のプログラムを作成してみましょう.

まずは,app/Main.hsをテキストエディタで開いて編集します.

app/Main.hsを開くと,以下のようなファイルになっているかと思います. Haskellのプログラムをコンパイルした実行可能ファイルでは,main = 内の記述が実行されます.

module Main (main) where

import Lib

main :: IO ()
main = someFunc

現在はsumFuncという関数が実行されます. sumFuncimport Lib の記述によって, src/Lib.hsからimportされています. src/Lib.hsを開くと,

module Lib
    ( someFunc
    ) where

someFunc :: IO ()
someFunc = putStrLn "someFunc"

という風にsomeFuncが定義されています. プログラム内の someFunc :: IO ()someFuncの型注釈です. IO () というのは,標準入出力 IO において, アクション () を実行するという意味ですが,ここではそれぞれの詳細は省きます. putStrLn は文字列を引数にとり,標準入出力IOに受け取った文字列を出力するというアクション()を返す関数であり,ここでは,"someFunc"という文字列が出力されます. この"someFunc" 部分を "Hello World"に書き換えれば,Hello Worldは実行できます.関数の定義はこのあと徐々に扱いますが, someFuncは,引数を取らないので関数というよりは実際には値です.

Lib.hshelloWorldと出力する値helloWorldを追加し,全体を以下のように書き換えましょう.

module Lib
    ( someFunc
    , helloWorld
    ) where

someFunc :: IO ()
someFunc = putStrLn "someFunc"

helloWorld :: IO ()
helloWorld = putStrLn "Hello World"

module Lib () where はモジュール宣言で,他のプログラムからimport Libで,src/Lib.hs内に定義された関数や値などの内 ()内に記述されたものを読み込むことができるようにします. 作成した値helloWorld()内にhelloWorldを追加することを忘れないようにしましょう.

併せて app/Main.hs を書き換えて,作成したhelloWorldを実行しましょう.

module Main (main) where

import Lib

main :: IO ()
main = helloWorld

このプログラムをコンパイルして得られる実行可能ファイルの名前などは,package.yaml内で定義されています.

ghc-options:
- -Wall
- -Wcompat
- -Widentities
- -Wincomplete-record-updates
- -Wincomplete-uni-patterns
- -Wmissing-export-lists
- -Wmissing-home-modules
- -Wpartial-fields
- -Wredundant-constraints

library:
  source-dirs: src

executables:
  hello-world-exe:
    main:                Main.hs
    source-dirs:         app
    ghc-options:
    - -threaded
    - -rtsopts
    - -with-rtsopts=-N
    dependencies:
    - hello-world

ghc-options: 以下の項目はghcのコンパイルオプションであり,Wで始まるいずれのオプションもコンパイル時のWarningを追加するものです. これらのコンパイルオプションがあると,プログラムの品質を高めることができますが, 利用していてWarningが邪魔に感じた場合は,すべて削除しても問題ありません( その場合は以下のように,ghc-options:部分を#でコメントアウトしてください.)

#ghc-options:

library:
  source-dirs: src

executables:
  hello-world-exe:
    main:                Main.hs
    source-dirs:         app
    ghc-options:
    - -threaded
    - -rtsopts
    - -with-rtsopts=-N
    dependencies:
    - hello-world

特に,本講義資料では,品質よりも分かりやすさを優先してできるだけシンプルな実装を紹介する他,事例としてあえて間違ったコードを入力する場面も存在します. そのままサンプルを入力すると多数のWarningが表示されることになるので,以下の説明中で登場する出力結果ではこれらのオプションはすべて切った状態のものとなっている点に留意してください.

library:以下の記述で,利用するライブラリのPATH,executables:以下の記述で実行可能ファイルについて記述されています. ここでは, executableとして’app’フォルダ内にある’Main.hs’が’hello-world-exe’という名称でコンパイルされることが書かれています.ghc-options:以下は,コンパイル時のオプションを設定していますが,ここでは詳細は省略します.

Main.hs以外のファイルをここに追加すれば,いくらでも実行可能ファイルは増やすことができます.

hello-world-exe部分をもっと短い名前に変更することも可能です.なお生成される実行可能ファイルはMacではhello-world-exe,Windowsではhello-world-exe.exeになるので注意してください.

それでは,以下のコマンドでこのプロジェクトをbuildして,実行してみましょう.

stack build
stack exec hello-world-exe

stack buildのあと,プログラムにミスがなければ以下のように出力されるはずです(一部省略しています).

 stack build
hello-world> build (lib + exe) with ghc-9.6.4
Preprocessing library for hello-world-0.1.0.0..
Building library for hello-world-0.1.0.0..
[1 of 2] Compiling Lib [Source file changed]
Preprocessing executable 'hello-world-exe' for hello-world-0.1.0.0..
Building executable 'hello-world-exe' for hello-world-0.1.0.0..
[1 of 2] Compiling Main [Source file changed]
[3 of 3] Linking .stack-work/dist/x86_64-osx/ghc-9.6.4/build/hello-world-exe/hello-world-exe [Objects changed]
hello-world> copy/register
Registering library for hello-world-0.1.0.0..

どこかで,タイプミスなどがあると例えば以下のようなエラーが表示される可能性もあります(一部省略しています).

hello-world> build (lib + exe) with ghc-9.6.4
Preprocessing library for hello-world-0.1.0.0..
Building library for hello-world-0.1.0.0..
Preprocessing executable 'hello-world-exe' for hello-world-0.1.0.0..
Building executable 'hello-world-exe' for hello-world-0.1.0.0..
[1 of 2] Compiling Main [Source file changed]

/Users/akagi/Documents/Programs/Haskell/blog/hello-world/app/Main.hs:6:8: error: [GHC-88464]
    Variable not in scope: hellWorld :: IO ()
    Suggested fix: Perhaps use ‘helloWorld’ (imported from Lib)
  |
6 | main = hellWorld
  |        ^^^^^^^^^

Error: [S-7282]
       Stack failed to execute the build plan.

       While executing the build plan, Stack encountered the error:

       [S-7011]
       While building package hello-world-0.1.0.0
       Process exited with code: ExitFailure 1

上のエラーでは, Main.hsの6行目で使用されている,hellWorldが定義されていないという意味になります. helloWorldoを追加して正しい名称にしたあともう一度 stack buildをしてみましょう.

stack exec hello-world-exeの後,Hello Worldと出力されていれば成功です.

なお,build と exec を併せて一つのコマンドstack run で代替することも可能です.

 stack run hello-world-exe
hello-world> build (lib + exe) with ghc-9.6.4
Preprocessing library for hello-world-0.1.0.0..
Building library for hello-world-0.1.0.0..
Preprocessing executable 'hello-world-exe' for hello-world-0.1.0.0..
Building executable 'hello-world-exe' for hello-world-0.1.0.0..
hello-world> copy/register
Registering library for hello-world-0.1.0.0..
Hello World
ce0f13b2-4a83-4c1c-b2b9-b6d18f4ee6d2