ペンギン村 Tech Blog

技術をこよなく愛するエンジニア集団が在住するペンギン村から、世界へ役立つ(かもしれない)技術情報を発信する技術系ブログです。某アラレちゃんが済む村とは一切関係ありません。んちゃ!

0. Introduction :: Swift プログラマのための Haskell 入門

Haskell は Swift に影響を与えたプログラミング言語として知られています。Swift に影響を与えたとされるプログラミング言語は他にも Objective-C、Rust、Ruby、Python、C# など多数に及びますが、プログラミングパラダイムに最も影響を与えているのは Haskell でないかと私は感じています。

この連載記事は、Swift と比較しながら Haskell を学んでいく記事です。

0. Introduction
1. 環境構築
2. 関数の基本
3. データ型の基本
4. リストと演算子と関数合成
5. リストと再帰
6. レコード構文と多相型とカインドと

-- About this articles?
readThisArticles :: SwiftProgrammer s => s -> HaskellerBeginner
readThisArticles = undefined

序文

近年、関数型プログラミングは”流行”を超えて一般的になりつつあり、関数型のアプローチは(部分的にしろ)現在主流のほとんどのプログラミング言語に何らかの形で導入されています。そうした中、純粋型関数型プログラミング言語である Haskell を学んでみようと思う人は少なからず居ると思います。

近頃は Haskell の入門書も増えており、以前に比べて Haskell は学びやすい環境になってきていますが、それでも Haskell は難しい言語であると巷では言われていますし、私自身もそうであると思っています。関数型言語特有の概念や考え方の難しさに加え、{} ベースのプログラミング言語(C言語の派生言語のこと)に慣れている人はインデントベースの独特のシンタックスに慣れるのにも苦労すると思います。

さて、新しいプログラミング言語を学ぶ際に効率的なテクニックは何でしょうか?いくつかアプローチがあると思いますが、その1つが自分の習得しているプログラミング言語と比較しながら理解を進めることです。それがこの連載記事(予定)の狙いで、Swift 言語と比較しながら Haskell について学べるような内容を目指しています。

つまり、以前の Go 言語でやったものの Haskell 版をやってみようという試みです。

blog.penginmura.tech

Haskell を学ぶモチベーション

新しいプログラミング言語を学ぶ際は、やはりモチベーションが重要となるでしょう。実際のところ Apple プラットフォームの開発に携わっている限り、Swift さえ覚えておけば大抵のことは何とかなるのは事実ですし、CI などは簡単なシェルスクリプトを書ければ十分とも言えます。そんな中、なぜ Haskell などという主流とは言えないプログラミング言語を学ぶ必要があるのでしょうか?

Swift も関数型プログラミング言語1と呼ばれており、関数は第一級オブジェクトとして扱えますし、代数的データ型(enum、struct など)や Protocol によるアドホック多相なども備えています。関数のカリー化や部分適用も、言語機能としては備わっていないものの、ライブラリなどを活用することで簡単に実現できます。そうしたことを考えると、あえて新しい関数型プログラミング言語を学ぶ意義は低いようにも見えます。

しかし、実際には Haskell というプログラミング言語はもっとずっと深遠なもので、Haskell を学ぶことで関数型プログラミングの真髄とも言えるものを習得できると私は思っています(ちょっと盛りすぎでしょうか?)。プログラミングのアプローチとしての関数型の考え方はもちろん、型についての深い知識、高カインド多相やそれを用いた「モナド」(そう、あのモナドです)、型レベルプログラミングなど、Swift では出てこない考え方も多く学ぶことができます。そうして学んだことは、間違いなく Swift でプログラミングする際にも役立つ知識となるでしょう。

”ハンマーを持つとすべてが釘に見える”というのはあまりに有名な言葉ですが、1つのプログラミング言語に拘っていると、特定の解決方法に囚われやすくなってしまいます。多くのプログラミング言語を学ぶのはとても良いことで、問題解決のアプローチを幅広くしてくれます。そういった意味でも、Swift に影響を与えた本格的な関数型プログラミング言語である Haskell を学ぶのは価値のある投資だと私は思っています。

事前注意

私は Swift も Haskell もそれなりには書けますが、両言語のプロフェッショナルやエキスパートでないことにご留意ください。Swift は中級者から上級者のあいだくらい、Haskell は初心者に毛の生えたくらいのスキルである、というのがおおむね妥当な評価かと思います。

そのため、本記事には知識不足ゆえの誤りが含まれる可能性があります。できるだけ注意して執筆しますが、誤りに気づいた方はコメントや Twitter の DM などでご連絡いただければと思います。

また、”連載記事”とは銘打っていますが、実のところほぼノープランで執筆を進めていく予定です。そのため、おそらく最適な構成にはならないと思います。もし、多くの方に読んでいただけるようであれば、連載を終えた後にあらためて書き直すかもしれません(これは余談ですが、リライトが最強の執筆テクニックであると知ったのは iOSテスト全書の執筆においてです)。

Introduction

さて、第0回目である今回は Haskell の簡単な紹介を行ってみたいと思います。おそらく分からないことや疑問点が出てくると思いますが、現時点では雰囲気を掴むだけで問題ありません。

Haskell の特徴を上げると以下のような感じでしょうか。

  • 純粋関数型
  • コンパイル言語
  • 静的型付け
  • 型推論
  • GC による自動メモリ管理
  • 強力な型システム
  • 遅延評価
  • 仕様と実装が分離している

純粋関数型

まず、Haskell は純粋関数型と呼ばれるプログラミング言語です。

(これはあまりに使い古された説明ですが)純粋関数型における「関数」とは数学的な意味での関数のことで、関数の出力はその関数の入力(引数)のみに依存し、関数内で外部の変数を読み書きしたり、I/O処理を行うなどの副作用を含んではいけないということです。

Swift のコードで言うと以下は純粋な関数です。

func add(x: Int, y: Int) -> Int {
    return x + y
}

そして以下はいずれも副作用を含むため、純粋な関数ではありません

var count = 0

func add(x: Int, y: Int) -> Int {
    count += 1 // NG: 外部の変数を読み書き
    return x + y
}

func add(x: Int, y: Int) {
    print(x + y) // NG: I/O処理を行っている
}

純粋関数型言語では、純粋な関数のみでプログラム全体を構築しなければならないという非常に大きな制約があります。変数の代入も副作用であるとみなされるため、Haskell では Swift でいうところの var な変数というものを使用することができません。

I/O処理が出来なければ、プログラミング言語としては役に立たないのではと疑問を持たれるかもしれませんが、Haskell には IO モナドというものが用意されており、その IO モナドの中でのみ I/O 処理を実行することが出来ます。

型システム

Haskell は Swift と同様にコンパイル言語で、静的型付けを持ち、型推論を備えており、コンパイル時に多くの誤りを検出できます。また、GC による自動メモリ管理を備えている点も同様です 2

そして、Haskell は Swift よりも強力な型システムを持っています。Idris 3 のような依存型こそ無いものの、高カインド多相を用いた Functor / Applicative Functor / Monad や型レベルプログラミングなど、より型を活用したプログラミングが可能になっています。

遅延評価

Haskell はプログラミング言語の評価戦略としては珍しい遅延評価を採用しています。

評価戦略は主に積極評価遅延評価に分けることができ、端的にいうと前者は引数を事前に評価するもの、後者は計算に必要になった時点で評価するというものです。

Swift は積極評価が採用されているため、引数の式は事前に評価されます。

func add(_ x: Int, _ y: Int) -> Int {
    return x + y // 引数`x`は事前に計算(`1 + 1`)され`2`が渡される
}
add(1 + 1, 2)

以下は Swift のコードで遅延評価をシミュレートしたものです。

func add(_ expr: @autoclosure () -> Int, _ y : Int) -> Int {
    return expr() + y // 引数`expr`は明示的に呼び出して初めて評価される
}
add(1 + 1, 2)

Haskell は遅延評価であるため、他のプログラミング言語ではあまり見られない無限リストといったものを扱うことができます。

nat = [1, 2..] -- 1, 2, 3, 4, 5, ... と続く自然数のリスト

仕様と実装の分離

最後に、Haskell はプログラミング言語としての仕様と実装が分離しています

そのため実装である Haskell の処理系は複数存在していますが、事実上は GHC(The Glasgow Haskell Compiler)一択という状況になっています。

www.haskell.org

Haskell は言語仕様として言語拡張を許しており、GHC は驚くほど多くの言語拡張を提供しています。記述ルールの拡張から、メタプログラミング、型レベルプログラミングなどが、GHC 拡張によって様々なことが可能になっています。

Are you ready?

さて、ここまで読んだあなたはきっと Haskell に興味を持ったことでしょう。あなたはすぐにでも Haskell の開発環境を整えようとしているかもしれませんが、次回の記事で VSCode と Stack による環境構築について取り上げるので、それを待つのも一考かもしれません。

blog.penginmura.tech

あとがき

2期でセイウンスカイちゃんの出番全然ないんですがそれは。

Written by tobi462.


  1. 一般的に関数を第一級として、他の型と同じように扱えるプログラミング言語のことを関数型言語と呼ばれることが多い気がします。

  2. ARC が GC に含まれるかどうかは度々議論になっている気がしますが、ここでは明示的にメモリの確保・開放を必要としない、という意味で使用しています。

  3. 依存型と呼ばれる「値に依存した型」を作れるプログラミング言語です・・・が、私もそれくらいの知識しかありませんので、今後の記事に Idris は登場しない予定です。