(調べると沢山出てくるが、ここでは平岡の授業の資料として 用意したものを2つ参照しておく:
コメント: --
から右が読み飛ばされる
関数の定義:
関数名 引数1 引数2... = 関数定義
関数を呼ぶ:
名前 引数1 引数2 ...
基本的にはスペースで区切って並べる(関数定義のときも同様)。
括弧は不要だが つけてもいい(呼出全体を囲む)。
( 名前 引数1 引数2 ...)
多くのプログラミング言語では関数呼出し(定義時も)で、関数名のあとに引数列だけを括弧で囲む流儀だが、
Haskell Lisp Scheme 等の関数型言語では全体を囲む
(Lisp系言語では囲むことが必須だがHaskellでは文法的 混乱防止上必要な時だけ使う)。
(実行もしてみて下さい)
repl.it のエディタペインに(内容が空のときに)表示されるメッセージで examples をクリックする(右図)と表示される(ここにも同じものを置いておきます)。
main という関数が呼び出されてプログラムがスタートする(このあたりは Cやjavaと近いね)。
do
は(実は糖衣構文だが)逐次実行のイメージで書く容器
<-
で、変数への代入に近いことを行っている
が、実際には「束縛」(再代入はできない)と呼ばれ、
<-
の右側に書かれた式に仮に名前を付けているだけ。
よくある言語(たとえばRuby)でこの行に似た表現だと、入力する関数(gets)が このタイミングで①呼び出され入力が実施され、その値が変数に②代入される、 という事象の発生順序が決まっている、がそれに対してHaskellでは、
サンプルではこのあと(の行で)、:
で連結したあと、 putStrLn
で出力が行わえる、その時には getLine
した値が実際に必要になる。
その必要になるぎりぎりのタイミングで、関数 getLine
が実際に呼び出され、入力が行われる。
FizzBuzz では「ガード」の使い方が示されている。
|
… =
の間に条件式が書かれる。`mod`
は、関数名を演算子のように中置で使うときの記法(後述の解説も参照)。Evens の例では、リスト内包表記、という記法(右図)が示されている。
出発点となるデータの集合体(左矢印 <-
の右側)から 要素を1つずつ取り出し、
それを変数(例ではx)に束縛(名前をつける)したとして、その結果、
縦棒 |
の左側の式の値となるものをリストとして再構成したもの、を表すのがリスト内包表記。
カンマ ,
の右側に条件式を書いた場合は、その式を満たすものだけが選ばれてリストに含まれることになる。
内包表記は Python, JavaScript, Erlang, Scala など 最近の多くの言語で採用されている。
ただし Rubyにはなく、Rubyでは同等の機能をメソッドチェーン(map, selectなどを使う)で実現している。
::
は、左辺の型指定を右辺で行うためのもの。
記号(演算子) $
もよく出てくるので知っておくといい(意味は後で解説する)。
まず、組み込み関数 putStrLn
を使ってみる。
puts
や JavaScript の Console.log
に近い意味。例えば以下のようなプログラムはエラーで動かない
main=putStrln 1
これをつきとめるために、REPLで :t
コマンド(:type コマンドの省略形)を使ってみる。
:t putStrLn -- 関数名
:t 1 -- 数値(これも関数のように扱われる)
その他なんでも、:t
の引数として渡すことができる。
足し算の演算子を見てみる。型シグニチャ (どの型のデータを受け取ってどの型の値を返すか) が表示される。
:t (+)
===> (+) :: Num a => a -> a -> a
この記号 +
は関数としても演算子としても使える(後述の解説も参照)
1 + 2 -- + だけで 演算子として
(+) 1 2 -- (+) という名前の関数として
型シグニチャの最後の単矢印の右側が、この関数が返す値の型。
その左の2つの a (型変数)が、引数の型。
Num a
の部分はその型がみたすべき条件(型クラス制約)。
(a, a) -> a
(引数2つなら普通はこうだろう)でなく、 a -> a -> a
という 直観的にわかりづらい表記になっているのは、理由がある (だんだん分かってきますが、とりあえずは これに慣れて下さい)。
数値あるいは数式の結果を putStrLn するためには、String に変換してから渡す 必要がある。
様々な型のデータ(制約 Show a を満たすもの)を文字列にする関数は show。
:t show
やってみよう。
putStrLn show 1
これはエラーになる。
関数の結果をさらに関数に(引数として)渡すときは下のように書く。
putStrLn (show 1)
-- または
putStrLn $ show 1
$
は、その右にあるもの全部(行末まで)をまとめて括弧 ( )
で囲んだのと同じ効果がある。
Rubyなどのオブジェクト指向言語では(下左図のように) オブジェクトにメソッド(関数)を適用した結果に、
さらに(右側にピリオド .
でつなげて)連鎖的にメソッドを 適用していくという流れで処理を実現できる「メソッドチェーン」という記法があり、
左から右へのコードの流れを作って行くことができる。
Haskell では、この $
を使って、右から左へと関数適用をバトンタッチしていく流れを作ることができる。
Haskellでは(多くのプログラミング言語と同様に)1+2
のような普通の数式が書ける。
+
)は、内部的には関数の一種として作られていて、 文法的に2つのオペランド(被演算子)の間に置いて使える工夫がされているもの (中置演算子などとも呼ぶ)。 演算子でない普通の名前の関数も、バッククオート ` `
で囲むことで 中置演算子的に使うことができる(前出の mod
がその例)。
( )
で囲むことで、 関数名と同様のものとして関数呼出しの先頭に書くこともできる。これらを整理したものが右板書図。
(+) 1 2 -- と、
1 + 2 -- とは同じ意味。
-- また、
5 `mod` 2 -- と、
mod 5 2 -- とは同じ意味。
こんなイメージのデータ構造。
先頭からのアクセスは書きやすい(効率もいい)が、末尾からは苦手。
先頭(の要素 = head) と、残り全部(リストとして = tail)、 という区分けを行う。
head と tail が、head : tail
として連結されたもの、と見ることができる。
残り全部、の部分に対する処理は、関数の再帰呼出で実現できる。
例: 関数 sum と同じ機能の、配列の要素全部を(加算して)合計した値を出す関数 sm を(再帰的に)作ってみる
sm :: Num a => [a] -> a -- リストを渡し集約値を1つ得る
sm [] = 0 -- リストの末端(これ以上再帰呼出させない)
sm (n : ns) = n + sm ns -- headの値と残り全部のsm を足し合わせる
ということは末端(空リスト)以外では2つめの定義が選ばれるということになる。
2つめの定義では、与えた引数を head部(n)とtail部(ns)に分けて受け取っている。
このように関数の仮引数を単純な変数名でなく構造として記述してパターンマッチで受け取ることができる機能は、 関数型(の機能を積極的に取り込んでいる)言語ではよく見られる。
リスト内包表記や map, filter, fold系の関数(高階関数)などでまとめて扱うことも勿論可能。
例:
sum lst = foldl (+) 0 lst
連結の演算子 :
でつなぐことができる。
1 : 2 : 3 : []
と [1,2,3]
が同じ意味。[1..100]
のような式(これは他の言語にもある)である範囲のリストが作れる。
だけではなく、[1..]
もある(が、これをいきなり表示させることは避けたほうがいい。
take 20 [1..]
head [1..]
(!!) [1..] 3