以下は、現時点での私なりの考えです。
関数とは、引数(ひきすう)と呼ばれるデータを与えると、あらかじめ決めておいた計算をして、答えを返してくれる仕組みです。この返してくれる答えのことを「戻り値」もしくは「返り値」と言います。そして、関数に与える引数の値が同じであれば、戻り値もいつも同じであることが保証されているプログラミング言語のことを純粋関数型言語と言います。
Haskellは純粋関数型言語だと言われています。この、同じ引数に対しては戻り値もいつも同じだということは当たり前のような気もしますが、ほかのことは何もしないという意味でもあります。このように関数は、与えらた引数を計算して戻り値を返すだけで、他のことは何もしないという性質を、プログラミング用語では参照透過性と言います。以下にWikipediaの短い説明をあげておきます。
参照透過性(さんしょうとうかせい、英: Referential transparency)は、計算機言語の概念の一種である。 ある式が参照透過であるとは、その式をその式の値に置き換えてもプログラムの振る舞いが変わらない(言い換えれば、同じ入力に対して同じ作用と同じ出力とを持つプログラムになる)ことを言う。 Wikipedia参照
しかし、関数を実行した結果が、たとえ文字列だけであっても、ディスプレイなどに表示されなければ、実際に何をしているのか分かりません。このようにディスプレイの表示内容が変わることなどを、コンピュータ用語では副作用と言います。以下にWikipediaの短い説明もあげておきます。
プログラミングにおける副作用(ふくさよう)とは、ある機能がコンピュータの(論理的な)状態を変化させ、それ以降で得られる結果に影響を与えることをいう。 代表的な例は変数への値の代入である。 例えば与えられた数字を二倍して返す機能" double "があるとする。 Wikipedia参照
Hakellでは、参照透過性と副作用という矛盾する事柄をモナドという仕組みで解決しています。Haskellでは、モナドに対応した関数は、戻り値の中に「値」と「副作用を行う処理」が含まれています。
なお、Haskellのすべての関数がモナドに対応しているわけではありません。また値と言っても、「無」という値もあるということも覚えておいてください。
では、Haskellの勉強をはじめましょう。
好きなテキストエディタで次のコードを記述してください。ファイル名は好きな名前で結構です。拡張子は.hs
にします。
a = putStr "Hello"
ターミナルもしくはコマンドプロンプトでファイルを保存したディレクトリに移動してださい。そして次のように入力して ghc を使ってファイルをコンパイルします。ファイル名のところはあなたがつけたファイル名です。
ghc ファイル名
次のようなエラーが出ると思います。
The IO action ‘main’ is not defined in module ‘Main’
プログラムのスタート地点である main が定義されていないという意味です。ghc でコンパイルするには、ファイルに main が定義されている必要があります。今度は次のように入力して、ファイルを ghci (Haskellのインタープリター) に読み込みます。
ghci ファイル名
次のように表示されれば、読み込みは成功しています。
Ok, modules loaded: Main.
*Main>
ghci では自動的に main が定義されてあるものとして認識されます。
次に a と入力して、プログラムを実行してください。
*Main> a
Hello*Main>
putStr
は、モナド型の関数で、副作用として文字列を出力しますが、改行はしません。コードを次のように変更してください。putStr
をputStrLn
に変えるだけです。
a = putStrLn "hello"
次のどちらかを入力してファイルを読み込み直してください。:l
は指定したファイルを読み込みます。:r
は最後に読み込んだファイルを読み込み直します。
*Main> :l
ファイル名*Main> :r
そして、もう一度 a
と入力してください。
*Main> a
Helllo
*Main>
Hello
と正しく表示されたと思います。putStrLn
もモナド型の関数で、副作用として文字列を表示してから改行をします。
ghci では、:t 識別子
と入力することで、その識別子の引数の型と戻り値の型を表示できます。
*Main> :t putStr
putStr :: String -> IO ()
*Main> :t putStrLn
putStrLn :: String -> IO ()
*Main>
putStr
も putStrLn
も String
(文字列) 型の引数を取り、IO ()
型を返しています。
引数としては、確かに文字列を受け取っていますが、戻り値として文字列を返しているわけではありません。戻り値は、IO
というモナド型です。そして ()
は、ユニットと呼ばれる値がないことを表す式です。コマンドプロンプトもしくはターミナルへの文字列の表示は、あくまでも副作用として処理されています。
このようにモナドを返す関数のことを「アクション」と呼びます。特に、IO
モナド型を返すアクションのことを「IO
アクション」と呼びます。
ファイルを次のように変更してください。
a = putStr "Hello"; putStr " "; putStrLn "Haskell"
そして、ファイルを読み込み直します。しかし次のエラーが出ると思います。
Failed, modules loaded: none.
Prelude>
Haskellでは、一つの識別子に一つの式しか束縛できません。束縛したいのが複数のアクションである場合は、do
を使って、複数のアクションを一つのアクションとみなして、一つの識別子に束縛することができます。コードを次のように変更してください。do
を追加しているだけです。
なお、アクション以外の式は、do
を使っても、エラーにはなりませんが、うまく一つの式にはなりません。
a = do putStr "Hello"; putStr " "; putStrLn "World"
:r
でファイルを読み込み直し、a
と入力して再度実行してください。
Helllo Haskell
正しく表示されたと思います。このように複数のアクションを一つのアクションにまとめることを「直列化」と言います。今回は、学習のためにアクションを ;
を区切りましたが、Haskellでは次のようにインデント(字下げ)を使って区切るのが一般的です。インデントの数が一致している間、do
のブロック内にあります。
a = do
putStr "Hello"
putStr " "
putStrLn "Haskell"
また、今回は学習のために、文字列をわざわざ三つのアクションに分割しましたが、次のように一つのアクションで書くの普通です。この場合は、do
は必要なくなります。
a = putStrLn "Hello Haskell"
文字列を表示するアクションは、putStr
、putStrLn
のほかに、print
というアクションがあります。print
は文字列を表示してから改行をします。戻り値の型は IO ()
です。
a = print "Hello Haskell"
*Main> a
"Hello Haskell"
*Main>
print
で表示された文字列は、上記のように二重引用符 "
で囲まれます。
「先ほどから使っている識別子 a
は変数です。Haskell では、変数に式を束縛することができます。Haskell の変数は値を変更することができません。そのため、変数に値を与えることを、代入と言わずに「束縛」と言います。
識別子の外で定義された変数をトップレベル変数と呼びます。トップレベル変数は、そのファイルのどこからでも参照することができます。参照元よりも後に記述しても大丈夫です。
a = putStrLn b
b = "Hello Haskell"
*Main> a
Hello Haskell
*Main> b
"Hello Haskell"
*Main>
特定の識別子内で定義され、その識別子内だけで使う変数をローカル変数と呼びます。ローカル変数を定義するには、where
か、let
というキーワードを使います。ローカル変数がトップレベル変数と同じ名前だった場合は、その識別子内ではローカル変数が優先されます。
a = putStrLn b
where
b = "I am a local vairable"
b = "Hello Haskell"
*Main> a
I am a local vairable
*Main> b
"Hello Haskell"
*Main>
where
は、節です。したがって識別子 a
は、依然として一つのアクションなので do
は必要ありません。where
節は、式全体の終わりで定義する点が特徴的です。
a = do
let b = "I am a local variable"
putStrLn b
b = "Hello Haskell"
実行結果は先ほどと同じになります。where
とは違って、let
を使ってローカル変数を定義する場合は、do
を使う必要があります。また、let
で定義されるローカル変数は、使われる前に定義しておく必要があります。
a =
let b = "I am a local variable"
in
putStrLn b
b = "Hello Haskell"
let
の使い方としては、この例のように、in
を使うことが基本形です。この場合、変数 a
の中は、let in
式を使った一つ式、putStrLn b
で終わる、一つアクションになりますので、do
は必要なくなります。つけても問題は起きません。なお、in
を字下げすることによって、式が続いていることを表しています。
ソースコードの中にメモを残しておきたい場合があります。そういう場合は、コメントという機能を使います。コメントを使った部分は、ghc
のコンパイル時や、ghci
の実行時には無視されます。
Haskellでは、−− と記述すると、その場所からその行の終わりまでがコメントとなります。また、{− と −} で囲むことによって、行の一部をコメントにしたり、複数行をコメントにすることができます。
-- let - in sample
a = -- variable a
let b {- variable b in a -} = "I am a local variable"
in
putStrLn b
b = "Hello Haskell"
{-
January 19, 2019
appdesign.jp
-}
Haskellでは、関数と変数の識別子は小文字で始めなければなりません。これは、慣習として決まっているわけではなく、言語の仕様として決まっています。関数や変数の識別子を大文字で始めた場合は、エラーとなります。
また、型クラスとコンストラクタと呼ばれるものの識別子は大文字で始めなければなりません。こちらも言語の仕様として決まっています。型クラスやコンストラクタの識別子を小文字で始めた場合は、エラーとなります。
$
を記述すると、その場所から式の終わりまで括弧 ( ) で囲んだことになります。行の終わりではなく式の終わりであることに注意してください。
a = do
print $ 10 + 4
print $ 10 - 4
print $ 10 * 4
print $ 10 / 4
print $ div 10 4
print $ mod 10 4
print $ 10 `div` 4
print $ 10 `mod` 4
14
6
40
2.5
2
2
2
2
かけ算は*
と記述します。
わり算は/
と記述します。/
による割り算は、答えが浮動小数点数になります。
わり算の答えを整数で欲しい場合はdiv
を使います。div
は整数にしか使えません。div
は計算する二つの値の前に記述します。div
をバッククォート `
で囲むと二つの値の間で使えるようになります。
わり算の余りを整数で欲しい場合はmod
を使います。mod
は整数にしか使えません。mod
は計算する二つの値の前に記述します。mod
をバッククォート `
で囲むと二つの値の間で使えるようになります。
a = do
print $ 10 == 4
print $ 10 /= 4
print $ 10 > 4
print $ 10 < 4
print $ 10 >= 4
print $ 10 <= 4
False
True
True
False
True
False
==
は等しい、/=
は等しくない、>
は大きい、<
は小さい、>=
は以上、<=
は以下です。
結果として表示されるTrue
は、真 (合っている) という意味です。結果として表示されるFalse
は、偽 (合っていない) という意味です。
論理演算子 ||
を使うと論理和が求められます。
a = do
print $ False || False
print $ False || True
print $ True || False
print $ True || True
*Main> a
False
True
True
True
論理和は左辺と右辺のどちらかが真であればTrue
を返します。
次のように括弧()
で囲んで関数のように使うこともできます。
(||) False False
論理演算子 &&
を使うと論理積が求められます。
a = do
print $ False && False
print $ False && True
print $ True && False
print $ True && True
*Main> a
False
False
False
True
論理和は左辺と右辺の両方が真であればTrue
を返します。
次のように括弧()
で囲んで関数のように使うこともできます。
(??) False False
論理演算子 not
を使うと逆の真偽値が求められます。
a = do
print $ not False
print $ not True
*Main> a
True
False