Haskell   基本

ホーム   目次


はじめに

以下は、現時点での私なりの考えです。

関数

関数とは、引数(ひきすう)と呼ばれるデータを与えると、あらかじめ決めておいた計算をして、答えを返してくれる仕組みです。この返してくれる答えのことを「戻り値」もしくは「返り値」と言います。そして、関数に与える引数の値が同じであれば、戻り値もいつも同じであることが保証されているプログラミング言語のことを純粋関数型言語と言います。

参照透過性

Haskellは純粋関数型言語だと言われています。この、同じ引数に対しては戻り値もいつも同じだということは当たり前のような気もしますが、ほかのことは何もしないという意味でもあります。このように関数は、与えらた引数を計算して戻り値を返すだけで、他のことは何もしないという性質を、プログラミング用語では参照透過性と言います。以下にWikipediaの短い説明をあげておきます。

参照透過性(さんしょうとうかせい、英: Referential transparency)は、計算機言語の概念の一種である。 ある式が参照透過であるとは、その式をその式の値に置き換えてもプログラムの振る舞いが変わらない(言い換えれば、同じ入力に対して同じ作用と同じ出力とを持つプログラムになる)ことを言う。 Wikipedia参照

副作用

しかし、関数を実行した結果が、たとえ文字列だけであっても、ディスプレイなどに表示されなければ、実際に何をしているのか分かりません。このようにディスプレイの表示内容が変わることなどを、コンピュータ用語では副作用と言います。以下にWikipediaの短い説明もあげておきます。

プログラミングにおける副作用(ふくさよう)とは、ある機能がコンピュータの(論理的な)状態を変化させ、それ以降で得られる結果に影響を与えることをいう。 代表的な例は変数への値の代入である。 例えば与えられた数字を二倍して返す機能" double "があるとする。 Wikipedia参照

モナド

Hakellでは、参照透過性と副作用という矛盾する事柄をモナドという仕組みで解決しています。Haskellでは、モナドに対応した関数は、戻り値の中に「値」と「副作用を行う処理」が含まれています。

なお、Haskellのすべての関数がモナドに対応しているわけではありません。また値と言っても、「無」という値もあるということも覚えておいてください。

Hello

では、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は、モナド型の関数で、副作用として文字列を出力しますが、改行はしません。コードを次のように変更してください。putStrputStrLnに変えるだけです。


a = putStrLn "hello"
    

次のどちらかを入力してファイルを読み込み直してください。:l は指定したファイルを読み込みます。:r は最後に読み込んだファイルを読み込み直します。

そして、もう一度 a と入力してください。

*Main> a
Helllo
*Main>

Hello と正しく表示されたと思います。putStrLnもモナド型の関数で、副作用として文字列を表示してから改行をします。

IO アクション

ghci では、:t 識別子 と入力することで、その識別子の引数の型と戻り値の型を表示できます。

*Main> :t putStr
putStr :: String -> IO ()
*Main> :t putStrLn
putStrLn :: String -> IO ()
*Main>

putStrputStrLnString (文字列) 型の引数を取り、IO () 型を返しています。

引数としては、確かに文字列を受け取っていますが、戻り値として文字列を返しているわけではありません。戻り値は、IO というモナド型です。そして () は、ユニットと呼ばれる値がないことを表す式です。コマンドプロンプトもしくはターミナルへの文字列の表示は、あくまでも副作用として処理されています。

このようにモナドを返す関数のことを「アクション」と呼びます。特に、IO モナド型を返すアクションのことを「IO アクション」と呼びます。

do

ファイルを次のように変更してください。


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"
    

print

文字列を表示するアクションは、putStrputStrLn のほかに、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 というキーワードを使います。ローカル変数がトップレベル変数と同じ名前だった場合は、その識別子内ではローカル変数が優先されます。

where


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節は、式全体の終わりで定義する点が特徴的です。

let


a = do
    let b = "I am a local variable"
    putStrLn b

b = "Hello Haskell"
    

実行結果は先ほどと同じになります。where とは違って、let を使ってローカル変数を定義する場合は、do を使う必要があります。また、let で定義されるローカル変数は、使われる前に定義しておく必要があります。

let - in


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


13842 visits
Posted: Jan. 20, 2019
Update: Jan. 22, 2019

ホーム   目次   ページトップ