Start Haskell2 に行ってきました

すごいHaskellたのしく学ぼう!の読書会としてStart Haskell2に行ってきました。

すごいHaskellたのしく学ぼう!

すごいHaskellたのしく学ぼう!

0. Introduction

http://www.slideshare.net/YasuyukiOgawa/ss-13433189

Haskellとは?
  • Haskellは純粋関数型言語
  • 純粋関数型言語は副作用を持たない
  • 副作用を持たない場合、同じ値を入力すれば同じ値を返す。(参照透過(透明)性)
  • 参照透過性を利用することで、Haskellは怠け者(lazy)になれる
  • 怠け者であるので、評価を必要になるまで遅延させる(遅延評価、call by need)
    • そのため、定義した段階では一見無理そうでも、実際に使う時までは評価されない。
  • 型推論ができる。そしてエレガントにて簡潔。
Haskellには型クラスがある

(+) :: Int -> Int -> Int と (+) :: Float -> Float -> Float に対して、
同じ+記号を使いたいけど、型はどうしたらよいかという問題に対して、型クラスが提案された。
この場合は、IntやFloatのように、足し算できる型の集合をNumクラスとする。
Javaで言えばインタフェースのような概念だが、違いは追々分かっていくらしい(`・ω・´)。
Numクラスを用いた(+)の定義は次の通り。

(+) :: Num a => a -> a -> a
環境とか

GHC: Glasgow Haskell Compiler (G is not GNU)
Haskellは仕様、GHCは実装の1つ。
Haskell Platform : GHCとよく使われるライブラリをセットにしたもの。
各OS用に合わせたGHCHaskell Platformを入れる事で使う事ができる。
ちなみに、単にGHCを入れて動作を動かそうとDLページに行くと、公式にSTOPがかかる(参考)。
他の環境として、Hugs*1があるが、今はメンテされていないので忘れていいらしい(´・ω・`)。
他の拡張環境もGHCをベースとしたものが多い。
また、Haskell' (Haskell Prime)は理想に走りすぎたので忘れていいらしい(´・ω・`)。

1.はじめの第一歩

VMwareに入れていた仮想環境が壊れていたので実施できず……(´・ω・`)
他の言語とちょっと違うHaskellの書き方について。

-- 真偽値の先頭は大文字。
True
False 

-- 関数に渡す引数はスペース区切りで(,)じゃないよ。
min 1 2 
  -- min(1, 2) ;; NG

-- 負数の場合は()を付ける。(構文解析の制約上)
min (-1) (-2)

-- コンパイルエラー(文字列と0なんてそもそも比較できないよね!)
"" == 0

-- これはTrueになる(共通の型クラス、理由は後述)
1 == 1.0

-- 関数を中置で使う場合はバッククォート(`)を利用する。
10 `div` 2  -- div 10 2 と同じ

-- Haskellの文字列は文字のリスト
"hoge" == ['h', 'o', 'g', 'e']
"hoge" == 'h' : 'o' : 'g' : 'e' : []

-- リスト操作関数色々
head [1,2,3] -- 1
tail [1,2,3] -- [2,3]
take 2 [1,2,3] -- [1,2]
elem 3 [1,2,3] -- True
elem 4 [1,2,3] -- False

-- リスト内容表記
[ x | x <- [1 .. 10], x `mod` 2 == 0] -- [2,4,6,8,10]
[ x*2 | x <- [1 .. 10], x `mod` 2 == 0 ] -- [4,8,12,16,20]

-- bit演算
import Data.Bits
15 .&. 9 -- 9
15 .|. 9 -- 15
15 `xor` 9 -- 6
Haskellにはオフサイドルールがある

Pythonのように字下げに意味があるので、コンパイルエラーとなる場合はその点を見てみる。

Hoogleを使おう

Java APIのようにどのような関数があるかはHoogle(など)にリファレンスとしてまとまっている。
機能に困ったら検索してみるのが吉。

演習問題

http://wiki.haskell.jp/Workshop/StartHaskell/LYHGG/exercise/1

解決のヒント

次の違いがある。(厳密な型は異なるが、今回の問題の範囲ではこの理解で十分)

  • (/) :: Float -> Float -> Float
  • div :: Int -> Int -> Int

レンジ(範囲指定生成)で減算を行う場合は2個目を指定する。

  • ['z', 'y' ... 'a']

問題6: 1から10までの全ての整数で割り切れる最少の正の整数を求めよ。

-- ここまでの知識のみの解法1
let res6 = head [x | x <- [1 .. ], x `mod` 2 == 0, x `mod` 3 == 0, x `mod` 4 == 0, x `mod` 5 == 0, x `mod` 6 == 0, x `mod` 7 == 0, x `mod` 8 == 0, x `mod` 9 == 0, x `mod` 10 == 0] 
-- ここまでの知識のみの解法2
let res6 = head [x | x <- [1 .. ], length [c | c <- [1..10], mod x c == 0] == 10]
-- all関数を利用したもの。解法1をスマートに書くとこうなる。
let res6 = head [x | x <- [1 .. ], all (\y -> x `mod` y == 0) [1..10]]

問題7: ピタゴラス数の組(a,b,c)(ただし、a

let res7 = head [ (a, b, c) | a <- [1..1000], b <- [a+1..1000], c <- [b+1..1000], a+b+c == 1000, a*a+b*b == c*c]

ただし、a,bが決まればcも決まるので(==1000制約から)、それを利用して2重ループ扱いとするのがよい。
あとは、上記の場合は制約a+b+c == 1000条件に当てはまらないものを先に書いた方が効率が良い。

head [(a,b,1000-a-b) | a <- [1..998], b <- [a+1..999], a^2+b^2 == (1000-a-b)^2 ]

2.型を信じろ

http://www.slideshare.net/skoji/haskell2-13424552

型を調べたり、たくさんある組み込みの型の紹介。

  • :t で型を調査。
  • Int(有界)、Integer(上下限なし)、Char(Unicode文字列)、タプル(a, b)などなど
  • 多相型・型変数:(例)タプル(a, b)

ここでのa, bは何の型でもよい。Generics的だがもっと強力!

  • 多相型・型クラス制約。 (==) :: (Eq a) => a -> a -> Bool

aは何でもよいが、Eq型クラスのインスタンスである必要があるという制約がつく。

型クラスの例
  • Eq型クラス:等価性判定ができる型のクラス
  • Ord型クラス:順序付けができる型のクラス
  • Show型クラス:文字列として表現できる型のクラス。show関数で文字列に変換可能。
  • Read型クラス:読み取り、文字列に変換できる型のクラス
  • Enum型クラス*2:順番に並んだ型のクラス。より具体的にはsuccで次の値が取得できる型のクラス。
  • Bounded型クラス:上限と下限を持つ型のクラス。maxBound/minBound関数で上下限がそれぞれ取得できる。
  • Num型クラス:数のように振る舞う型のクラス*3
  • Floating型クラス:浮動小数点の型のクラス
  • Integral型クラス:整数全体の型のクラス
型注釈

実際の型があいまいで一意に推論できない場合などに型を指定できる。
(5 :: Int) のように、値の後ろに":: 型名"を記述することで利用できる。

Haskellでは自由に2項演算子を自由に定義できる(=Haskellでは2引数関数は2項演算子と等価)
型クラスによって、複数の型で同様の記号・関数名を利用できる。

コンパイラによるリテラルの解釈

Haskellでは、2 + 2.5の式はコンパイルエラーにならない。
これは一見、Int + Floatの足し算を行うようにみえるが、これは以下の型クラスの仕組みにより解決される。
(ただ、いまいち理解が錯綜したので、間違えている可能性は高いです。)

2とだけ直接プログラム上に記述した場合、それはNum型クラスのインスタンスである。
重要なのは、2と書いた段階でIntに型が確定するわけではないことである。
2も2.5も、それを書くだけではNum型クラスのインスタンスとみなされる。
そのため、この時点で2 + 2.5はNum aを返す式として解釈され、それ以上の評価は行われない
(のが、GHCのデフォルトの挙動っぽい。そして、この挙動をある程度自由に変化させられるらしい?)


一方で、これを表示する場合には、具体的な計算結果を評価する必要がある。
この時に初めて、具体的に式の結果が最終的にはどの型になるか(型クラスから型になるか)を決定する必要がある。
(補足:型クラスは具体的な型ではないため、このままでは結果となる値が属する最終的な型が決定できない。)
また、(+) :: (Num a) => a -> a -> a であり、式を解析する時点で初めてNum型の変数が(具体的に)どの型であるかの推論が行われる。
そのため、この式を解決するためには、aはFloat(あるいは、2.5を解決できる)Num型クラスのインスタンスである事が求められる。
ここで1からIntで解釈を行おうとすると、2.5が解決できないため、2.5が解決できるFractional aが適用され、
結果として、(+) :: Fractional a => a -> a -> a を今回の場合には適用することを決定する。
この後、Fractional型クラスのインスタンス変換時にHaskellのdefaultingルールによりDoubleに変換される。
それゆえ、2 + 2.5は解決でき、結果として2.0*4 + 2.5と(結果的に)等価となる。
また、この仕組みにより、1 == 1.0も同様にTrueとなる(リテラル1がFloat型と解釈された結果と1.0 :: Doubleを比較するため)。

演習問題

http://wiki.haskell.jp/Workshop/StartHaskell/LYHGG/exercise/2

型クラスが入る場合の記述

-- 関数名 :: (型クラス制約) => [関数定義]
-- show関数 :: Show型クラスのインスタンスを受け取りStringを返す関数
show :: Show a => a -> String

id関数の有用性:関数合成の単位元*5として有用。

すごいHaskellのテストたのしく学ぼう(LT)

スライド: http://www.slideshare.net/ShokoSasaki/haskell-13433770
GitHub: https://github.com/shokos/Haskell_test_tut

Cabalで「cabal install HUnit」を実施することでHUnitを入手してから。
こんな感じで、「assert関数、メッセージ、期待値(テストが成功となる値)、テスト内容」のリストを作成する。

-- リンク先(https://github.com/shokos/Haskell_test_tut)より引用
--assertの配列
caliculateTests :: [Test]
caliculateTests = map TestCase
    [ assertEqual "plus 1 2" 3 (plus 1 2)
     ,assertEqual "minus 5 3" 2 (minus 5 3)
    ]

Haskellを勉強するために (LT)

  • Hoogle

もはやサンプルと関数名と型で何がやりたいかが大体分かるレベル。

実際にHaskellを使って問題を解こう!

google: 48時間でschemeを書こう で検索をして実際に

  • Webフレームワーク: Yesodを使おう!

現在Haskellで最も実用的なWebフレームワーク
マイクロフレームワークなんかもいいかも?

番外編

Q.関数型言語での大規模設計とかどうやるの?
A.UMLのクラスと対応するのは型クラスを含む型システム。
とはいえ、ほとんどオブジェクト指向設計と変わらない。
ただ、HaskellJavaなどと違うのはデータが関数と対等。
デザインパターンというノウハウは言語が貧弱だから使わざるを得ない。
事実、Haskellではデザインパターンのように名前を付ける必要すらないほどに簡単に書ける。
(Strategyとか、そもそも高階関数さえあれば要らないよね、というごもっともな話。)

すごいYesodたのしく学ぼう! (LT)

(注:Haskell Day 2012 のスライドと同じ)
http://www.slideshare.net/ssuser6c06ba/20120527yesod

コーディングしている分には、他の言語でWebサイトを書くのとあまり変わらない。
コンパイル時の型チェック(アプリ内リソースに対してリンク切れが無い)とか、各セキュリティ対策がデフォルトで効いているとか。

最後に

一回目から非常に熱い議論(特にNum型に対する)が行われましたこともあり、今後どのようになっていくかが楽しみです。
自分の中での3度目のStartをしたので、IOに包まれて汚れた経験者になって帰るまで是非続けたいと思います。

*1:プログラミングHaskellなどで名前が出てくるインタプリタ

*2:C/Javaenumとは少し意味づけが違うかも

*3:何を持って数のようであるのかの明記が無いが、原文ではIts instances can act like number.とあるため、大体現実世界の数値と同じようなものを表してるよ!という意味、かと。より厳密には型として持つべき制約があり、それら全ての実装が行われているクラスが所属する。

*4:リテラル2をDoubleと解釈した結果

*5:二項演算を行っても、結果が変わらない場合の値eを単位元という(定義があいまいすぎるが、ある程度分かりやすく記述する)。加算なら0、積算なら1。