Haskell-quick-guide
Haskell-概要
Haskellは、記号計算およびリスト処理アプリケーションを処理するために特別に設計された関数型プログラミング言語です。 関数型プログラミングは数学関数に基づいています。 Haskell以外にも、関数型プログラミングのパラダイムに従う他の一般的な言語には、Lisp、Python、Erlang、Racket、F#、Clojureなどがあります。
- 従来のプログラミング*では、命令は特定の構文または形式の宣言のセットとして扱われますが、*機能的なプログラミング*の場合、すべての計算は個別の数学関数の組み合わせと見なされます。
Haskellで機能する
Haskellは、広く使用されている純粋に機能的な言語です。 ここで、この言語をJava、C、C ++、PHPなどの他の従来のプログラミング言語よりも特別なものにするいくつかのポイントをリストアップしました。
- 関数型言語-従来のプログラミング言語では、コンパイラに一連のタスクを指示します。これらのタスクは、コンピュータに「何をするか」および「どのように行うか」を伝えることです。しかし、Haskellでは、コンピューターに「それは何ですか?」
- *怠azine *-Haskellは怠zyな言語です。 *怠y *とは、Haskellが理由なしに式を評価しないことを意味します。 評価エンジンは、式を評価する必要があると判断すると、*サンクデータ構造*を作成して、その特定の評価に必要なすべての情報とその*サンクデータ構造*へのポインタを収集します。 評価エンジンは、その特定の式を評価する必要がある場合にのみ機能を開始します。
- モジュール性-Haskellアプリケーションは、一連の機能に他なりません。 Haskellアプリケーションは、多数の小さなHaskellアプリケーションのコレクションであると言えます。
- Statically Typed -従来のプログラミング言語では、一連の変数とそのタイプを定義する必要があります。 対照的に、Haskellは厳密に型指定された言語です。 用語「厳密に型指定された言語」とは、Haskellコンパイラーが宣言された変数の型を把握するのに十分にインテリジェントであることを意味します。したがって、使用される変数の型を明示的に述べる必要はありません。
- 保守性-Haskellアプリケーションはモジュール式であるため、保守が非常に簡単で費用対効果に優れています。
機能的プログラムはより並行的であり、実行時の並列性に従ってより正確で優れたパフォーマンスを提供します。 Haskellも例外ではありません。 *マルチスレッド*を効果的に処理する方法で開発されました。
こんにちは世界
Haskellのダイナミズムを示す簡単な例です。 次のコードを見てください。 必要なのは、コンソールに「Hello Word」を印刷する1行だけです。
main = putStrLn "Hello World"
Haskellコンパイラが上記のコードに遭遇すると、すぐに次の出力を生成します-
Hello World
Haskellのパワーとシンプルさを示すために、このチュートリアル全体で多くの例を提供します。
Haskell-環境のセットアップ
Haskellプログラミング環境をオンラインでセットアップしました-https://www.finddevguides.com/compile_haskell_online.php
このオンラインエディターには、Haskellプログラミングの例を練習するためのオプションがたくさんあります。 ページの端末セクションに移動し、 "ghci" と入力します。 このコマンドはHaskellコンパイラを自動的にロードし、Haskellをオンラインで起動します。 ghci コマンドを使用すると、次の出力が表示されます。
sh-4.3$ ghci
GHCi,version7.8.4:http://www.haskell.org/ghc/:?forhelp
Loading package ghc-prim...linking...done.
Loading packageinteger gmp...linking... done.
Loading package base...linking...done.
Prelude>
それでもローカルシステムでHaskellをオフラインで使用する場合は、利用可能なHaskellセットアップを公式Webページからダウンロードする必要があります-https://www.haskell.org/downloads
市場で入手可能な3つの異なるタイプの*インストーラー*があります-
- *最小インストーラー-GHC(Glasgow Haskell Compiler)、CABAL(アプリケーションとライブラリを構築するための共通アーキテクチャ)、およびスタックツールを提供します。
- Stack Installer -このインストーラーでは、GHCをマネージドトールチェーンのクロスプラットフォームでダウンロードできます。 必要なときにいつでもAPIツールを更新できるように、アプリケーションをグローバルにインストールします。 Haskell指向のすべての依存関係を自動的に解決します。
- * Haskellプラットフォーム*-これは、Haskellをインストールする最良の方法です。プラットフォーム全体をマシンにインストールし、特定の場所からインストールするからです。 このインストーラーは、上記の2つのインストーラーのように配布されません。
市場で入手可能なさまざまなタイプのインストーラーを見てきたので、マシンでそれらのインストーラーを使用する方法を見てみましょう。 このチュートリアルでは、Haskellプラットフォームインストーラーを使用して、システムにHaskellコンパイラをインストールします。
Windowsでの環境設定
WindowsコンピューターにHaskell環境をセットアップするには、公式Webサイトhttps://www.haskell.org/platform/windowslにアクセスし、カスタマイズ可能なアーキテクチャーに従ってインストーラーをダウンロードします。
システムのアーキテクチャを確認し、対応するセットアップファイルをダウンロードして実行します。 他のWindowsアプリケーションと同様にインストールされます。 システムのCABAL構成を更新する必要がある場合があります。
MACで設定される環境
MACシステムにHaskell環境を設定するには、公式Webサイトhttps://www.haskell.org/platform/maclにアクセスして、Macインストーラーをダウンロードします。
Linuxでの環境設定
LinuxベースのシステムにHaskellをインストールするには、MACやWindowsのようにそれほど簡単ではないコマンドを実行する必要があります。 はい、面倒ですが、信頼できます。
以下の手順に従って、LinuxシステムにHaskellをインストールできます-
- ステップ1 *-LinuxシステムにHaskell環境をセットアップするには、公式Webサイトhttps://www.haskell.org/platform/linuxlにアクセスして、ディストリビューションを選択します。 ブラウザに次の画面が表示されます。
- ステップ2 *-ディストリビューションを選択します。 この例では、Ubuntuを使用しています。 このオプションを選択すると、ローカルシステムにHaskellをインストールするコマンドを含む画面に次のページが表示されます。
ステップ3 *-Ctrl + Alt + Tを押してターミナルを開きます。 *"$ sudo apt-get install haskell-platform" コマンドを実行し、Enterを押します。 rootパスワードで認証すると、システムにHaskellのダウンロードが自動的に開始されます。 インストール後、確認メッセージが表示されます。
- ステップ4 *-再度ターミナルに移動して、GHCIコマンドを実行します。 Preludeプロンプトが表示されたら、ローカルシステムでHaskellを使用する準備ができています。
GHCIプロローグを終了するには、コマンド「:quit exit」を使用できます。
Haskell-基本的なデータモデル
Haskellは純粋に機能的なプログラミング言語であるため、他のプログラミング言語よりもはるかにインタラクティブでインテリジェントです。 この章では、実際に事前定義されているか、何らかの形でインテリジェントにコンピューターのメモリにデコードされているHaskellの基本データモデルについて学びます。
このチュートリアルでは、Webサイト(https://www.finddevguides.com/codingground)から入手できるHaskellオンラインプラットフォームを使用します。
番号
Haskellは、ある数を数としてデコードするのに十分なインテリジェントです。 したがって、他のプログラミング言語の場合に通常行うように、そのタイプを外部で言及する必要はありません。 例として、プレリュードコマンドプロンプトに移動し、「2 + 2」を実行してEnterキーを押します。
sh-4.3$ ghci
GHCi, version 7.6.3: http://www.haskell.org/ghc/ :? for help
Loading package ghc-prim ... linking ... done.
Loading package integer-gmp ... linking ... done.
Loading package base ... linking ... done.
Prelude> 2+2
その結果、次の出力が表示されます。
4
上記のコードでは、2つの数値を引数としてGHCIコンパイラーに型を事前定義せずに渡しましたが、コンパイラーはこれら2つのエントリを数値として簡単にデコードできました。
次に、もう少し複雑な数学的計算を試して、インテリジェントコンパイラが正しい出力を提供するかどうかを確認しましょう。 「15+(5 * 5)-40」で試してください
Prelude> 15+(5*5)-40
上記の式では、予想される出力に従って「0」が生成されます。
0
キャラクター
数字と同様に、Haskellは入力された文字をインテリジェントに識別できます。 Haskellコマンドプロンプトに移動し、二重引用符または一重引用符で任意の文字を入力します。
次の行を入力として提供し、その出力を確認しましょう。
Prelude> :t "a"
それは次の出力を生成します-
"a" :: [Char]
入力の提供中に(: t )を使用することを忘れないでください。 上記の例では、*(:t)*は入力に関連する特定のタイプを含めることです。 このタイプの詳細については、今後の章で説明します。
次の例を見てください。ここでは、無効な入力をcharとして渡しているため、エラーが発生します。
Prelude> :t a
<interactive>:1:1: Not in scope: 'a'
Prelude> a
<interactive>:4:1: Not in scope: 'a'
エラーメッセージ "<interactive>:4:1:Not in scope:` a '"により、Haskellコンパイラは入力を認識できないことを警告しています。 Haskellは、すべてが数字を使用して表されるタイプの言語です。
Haskellは、従来のASCIIエンコーディングスタイルに従います。 もっと理解するために次の例を見てみましょう-
Prelude> '\97'
'a'
Prelude> '\67'
'C'
入力がどのようにASCII形式にデコードされるかを確認してください。
ひも
*string* は文字のコレクションにすぎません。 文字列を使用するための特定の構文はありませんが、Haskellは、二重引用符で文字列を表す従来のスタイルに従います。
文字列「finddevguides.com」を渡す次の例を見てください。
Prelude> :t "finddevguides.com"
画面に次の出力が生成されます-
"finddevguides.com" :: [Char]
文字列全体がCharの配列としてのみデコードされている方法を確認してください。 他のデータ型とその構文に移りましょう。 実際の練習を開始すると、すべてのデータ型とその使用法に慣れます。
ブール値
ブールデータ型も、他のデータ型と同様に非常に簡単です。 「True」や「False」などのブール入力を使用してさまざまなブール演算を使用する次の例を見てください。
Prelude> True && True
True
Prelude> True && False
False
Prelude> True || True
True
Prelude> True || False
True
上記の例では、「True」と「False」がブール値であることに言及する必要はありません。 Haskell自体がそれをデコードし、それぞれの操作を実行できます。 「true」または「false」で入力を変更しましょう。
Prelude> true
それは次の出力を生成します-
<interactive>:9:1: Not in scope: 'true'
上記の例では、Haskellは「true」と数値を区別できなかったため、入力された「true」は数値ではありません。 したがって、Haskellコンパイラは、入力がスコープではないことを示すエラーをスローします。
リストとリストの理解
他のデータ型と同様、 List もHaskellで使用される非常に便利なデータ型です。 例として、[a、b、c]は文字のリストです。したがって、定義により、Listはコンマで区切られた同じデータ型のコレクションです。
他のデータ型と同様に、リストをリストとして宣言する必要はありません。 Haskellは、式で使用されている構文を調べることで入力をデコードするのに十分インテリジェントです。
Haskellがリストをどのように扱うかを示す次の例を見てください。
Prelude> [1,2,3,4,5]
それは次の出力を生成します-
[1,2,3,4,5]
Haskellのリストは本質的に同質です。つまり、異なる種類のデータ型のリストを宣言することはできません。 [1,2,3,4,5、a、b、c、d、e、f]のようなリストはエラーを生成します。
Prelude> [1,2,3,4,5,a,b,c,d,e,f]
このコードは、次のエラーを生成します-
<interactive>:17:12: Not in scope: 'a'
<interactive>:17:14: Not in scope: 'b'
<interactive>:17:16: Not in scope: 'c'
<interactive>:17:18: Not in scope: 'd'
<interactive>:17:20: Not in scope: 'e'
<interactive>:17:22: Not in scope: 'f'
リストの理解
リストの理解は、数式を使用してリストを生成するプロセスです。 次の例を見てください。ここでは、[output |範囲、条件]。
Prelude> [x*2| x<-[1..10]]
[2,4,6,8,10,12,14,16,18,20]
Prelude> [x*2| x<-[1..5]]
[2,4,6,8,10]
Prelude> [x| x<-[1..5]]
[1,2,3,4,5]
数式を使用して1つのリストを作成するこの方法は、*リスト内包*と呼ばれます。
タプル
Haskellは、単一のデータ型で複数の値を宣言する別の方法を提供します。 Tuple として知られています。 タプルはリストと見なすことができますが、タプルとリストの間にはいくつかの技術的な違いがあります。
リストは変更可能なデータ型ですが、実行時に要素の数を変更できないため、タプルは不変のデータ型です。
一方、リストは同種のデータ型ですが、タプルには異種のデータが含まれている可能性があるため、タプルは本質的に異種です。
タプルは単一の括弧で表されます。 次の例を見て、Haskellがタプルをどのように扱うかを確認してください。
Prelude> (1,1,'a')
それは次の出力を生成します-
(1,1,'a')
上記の例では、2つの number 型変数と1つの char 型変数を持つ1つのTupleを使用しました。
Haskell-基本的な演算子
この章では、Haskellで使用されるさまざまな演算子について学びます。 他のプログラミング言語と同様に、Haskellは加算、減算、乗算などの基本的な操作をインテリジェントに処理します。 今後の章では、さまざまな演算子とその使用法について詳しく学習します。
この章では、オンラインプラットフォーム(https://www.finddevguides.com/codingground)を使用して、Haskellでさまざまな演算子を使用します。 整数*型番号のみを使用していることに注意してください。これは、後続の章で 10進数*型番号について詳しく学習するためです。
加算演算子
名前が示すように、加算(+)演算子は加算機能に使用されます。 次のサンプルコードは、Haskellで2つの整数を追加する方法を示しています-
main = do
let var1 = 2
let var2 = 3
putStrLn "The addition of the two numbers is:"
print(var1 + var2)
上記のファイルでは、2つの別個の変数 var1 および var2 を作成しました。 最後に、 addition 演算子を使用して結果を出力します。 compile および execute ボタンを使用して、コードを実行します。
このコードは、画面に次の出力を生成します-
The addition of the two numbers is:
5
減算演算子
名前が示すように、この演算子は減算演算に使用されます。 次のサンプルコードは、Haskellで2つの整数を減算する方法を示しています-
main = do
let var1 = 10
let var2 = 6
putStrLn "The Subtraction of the two numbers is:"
print(var1 - var2)
この例では、2つの変数 var1 および var2 を作成しました。 その後、減算(-)演算子を使用して2つの値を減算します。
このコードは、画面に次の出力を生成します-
The Subtraction of the two numbers is:
4
乗算演算子
この演算子は乗算演算に使用されます。 次のコードは、乗算演算子を使用してHaskellで2つの数値を乗算する方法を示しています-
main = do
let var1 = 2
let var2 = 3
putStrLn "The Multiplication of the Two Numbers is:"
print(var1 * var2)
このコードは、オンラインプラットフォームで実行すると、次の出力を生成します-
The Multiplication of the Two Numbers is:
6
除算演算子
次のコードを見てください。 Haskellで2つの数値を分割する方法を示しています-
main = do
let var1 = 12
let var2 = 3
putStrLn "The Division of the Two Numbers is:"
print(var1/var2)
それは次の出力を生成します-
The Division of the Two Numbers is:
4.0
シーケンス/範囲演算子
シーケンスまたは範囲は、Haskellの特別な演算子です。 「(..)」で示されます。 値のシーケンスを含むリストを宣言するときに、この演算子を使用できます。
1から10までのすべての値を印刷する場合、「[1..10]」のようなものを使用できます。 同様に、「a」から「z」までのすべてのアルファベットを生成する場合は、 "[a..z]" と入力するだけです。
次のコードは、シーケンス演算子を使用して1から10までのすべての値を印刷する方法を示しています-
main :: IO()
main = do
print [1..10]
それは次の出力を生成します-
[1,2,3,4,5,6,7,8,9,10]
Haskell-意思決定
意思決定は、プログラマーがコードフローに条件を適用できるようにする機能です。 プログラマは、事前定義された条件に応じて一連の命令を実行できます。 次のフローチャートは、Haskellの意思決定構造を示しています-
Haskellは、意思決定ステートメントの次のタイプを提供します-
Sr.No. | Statement & Description |
---|---|
1 |
|
2 |
複数の if ブロックとそれに続く else ブロック |
Haskell-型と型クラス
Haskellは関数型言語であり、厳密に型指定されています。つまり、アプリケーション全体で使用されるデータ型は、コンパイル時にコンパイラーに認識されます。
組み込み型クラス
Haskellでは、すべてのステートメントは数式と見なされ、この式のカテゴリは Type と呼ばれます。 「タイプ」は、コンパイル時に使用される式のデータ型であると言えます。
*Type* の詳細については、「:t」コマンドを使用します。 一般的な方法では、 *Type* は値と見なすことができますが、 *Type Class* は一連の類似した種類の種類と見なすことができます。 この章では、さまざまな組み込み型について学習します。
Int
*Int* は、整数型データを表す型クラスです。 2147483647〜-2147483647の範囲内のすべての整数は、 *Int* 型クラスに属します。 次の例では、関数* fType()*は定義されたタイプに従って動作します。
fType :: Int -> Int -> Int
fType x y = x*x + y*y
main = print (fType 2 4)
ここでは、関数* fType()のタイプを *int として設定しています。 この関数は2つの int 値を取り、1つの int 値を返します。 このコードをコンパイルして実行すると、次の出力が生成されます-
sh-4.3$ ghc -O2 --make *.hs -o main -threaded -rtsopts
sh-4.3$ main
20
整数
整数*は *Int のスーパーセットと見なすことができます。 この値は任意の数に制限されていないため、整数の長さには制限がありません。 Int 型と Integer 型の基本的な違いを確認するには、次のように上記のコードを変更しましょう-
fType :: Int -> Int -> Int
fType x y = x*x + y*y
main = print (fType 212124454 44545454454554545445454544545)
上記のコードをコンパイルすると、次のエラーメッセージがスローされます-
main.hs:3:31: Warning:
Literal 44545454454554545445454544545 is out of the Int range -
9223372036854775808..9223372036854775807
Linking main ...
このエラーは、関数fType()が1つのInt型値を予期しており、実際の大きなInt型値を渡すために発生しました。 このエラーを回避するには、タイプ「Int」を「Integer」で変更し、違いを観察しましょう。
fType :: Integer -> Integer -> Integer
fType x y = x*x + y*y
main = print (fType 212124454 4454545445455454545445445454544545)
今、それは次の出力を生成します-
sh-4.3$ main
1984297512562793395882644631364297686099210302577374055141
浮く
次のコードをご覧ください。 HaskellでのFloat型の動作を示しています-
fType :: Float -> Float -> Float
fType x y = x*x + y*y
main = print (fType 2.5 3.8)
この関数は、入力として2つの浮動小数点値を取り、出力として別の浮動小数点値を生成します。 このコードをコンパイルして実行すると、次の出力が生成されます-
sh-4.3$ main
20.689999
ダブル
*Double* は、末尾に倍精度の浮動小数点数です。 次の例を見てください-
fType :: Double -> Double -> Double
fType x y = x*x + y*y
main = print (fType 2.56 3.81)
上記のコードを実行すると、次の出力が生成されます-
sh-4.3$ main
21.0697
Bool
*Bool* はブール型です。 TrueまたはFalseのいずれかです。 HaskellでBool型がどのように機能するかを理解するには、次のコードを実行します-
main = do
let x = True
if x == False
then putStrLn "X matches with Bool Type"
else putStrLn "X is not a Bool Type"
ここでは、変数 "x"をBoolとして定義し、別のブール値と比較してその独自性を確認しています。 それは次の出力を生成します-
sh-4.3$ main
X is not a Bool Type
Char
Charはキャラクターを表します。 一重引用符内のすべては、キャラクターと見なされます。 次のコードでは、Char値を受け入れ、出力としてChar値を返すように、以前の* fType()*関数を変更しました。
fType :: Char-> Char
fType x = 'K'
main = do
let x = 'v'
print (fType x)
上記のコードは、 char 値 'v’で* fType()*関数を呼び出しますが、別のchar値、つまり 'K’を返します。 ここにその出力があります-
sh-4.3$ main
'K'
Haskellは型が宣言される前にその型をキャッチするのに十分なインテリジェントであるため、これらの型を明示的に使用しないことに注意してください。 このチュートリアルの以降の章では、異なる型とTypeクラスがHaskellを強く型付けされた言語にする方法を説明します。
EQタイプクラス
*EQ* 型クラスは、式の等価性をテストする機能を提供するインターフェイスです。 式の等価性をチェックするTypeクラスは、このEQ Type Classの一部である必要があります。
上記のすべての標準Typeクラスは、この EQ クラスの一部です。 上記の型のいずれかを使用して同等性をチェックするときはいつでも、実際には EQ 型クラスを呼び出しています。
次の例では、内部で「==」または「/=」操作を使用して EQ タイプを使用しています。
main = do
if 8/= 8
then putStrLn "The values are Equal"
else putStrLn "The values are not Equal"
次の出力が得られます-
sh-4.3$ main
The values are not Equal
順序型クラス
*Ord* は、順序付けの機能を提供する別のインターフェイスクラスです。 これまで使用してきた *types* はすべて、この *Ord* インターフェイスの一部です。 EQインターフェースと同様に、Ordインターフェースは「>」、「<」、「<=」、「> =」、「compare」を使用して呼び出すことができます。
このタイプクラスの「比較」機能を使用した例を以下に示します。
main = print (4 <= 2)
ここで、Haskellコンパイラは4が2以下かどうかをチェックします。 そうではないので、コードは次の出力を生成します-
sh-4.3$ main
False
Show
*Show* には、引数を文字列として出力する機能があります。 引数が何であれ、結果を常に文字列として出力します。 次の例では、このインターフェイスを使用してリスト全体を印刷します。 「show」を使用してこのインターフェイスを呼び出すことができます。
main = print (show [1..10])
コンソールに次の出力が生成されます。 ここで、二重引用符は、文字列型の値であることを示しています。
sh-4.3$ main
"[1,2,3,4,5,6,7,8,9,10]"
Read
*Read* インターフェースはShowと同じことをしますが、結果を文字列形式で出力しません。 次のコードでは、 *read* インターフェイスを使用して文字列値を読み取り、それをInt値に変換しています。
main = print (readInt "12")
readInt :: String -> Int
readInt = read
ここでは、ストリング変数( "12")を readInt メソッドに渡し、変換後に12(Int値)を返します。 ここにその出力があります-
sh-4.3$ main
12
Enum
*Enum* は、Haskellのシーケンシャルまたは順序付けられた機能を有効にする別のタイプのクラスです。 このTypeクラスには、 *Succ、Pred、Bool、Char* などのコマンドでアクセスできます。
次のコードは、12の後続値を見つける方法を示しています。
main = print (succ 12)
それは次の出力を生成します-
sh-4.3$ main
13
跳ねる
上限と下限を持つすべてのタイプは、このタイプクラスに属します。 たとえば、 Int タイプのデータには、「9223372036854775807」の上限と「-9223372036854775808」の下限があります。
次のコードは、HaskellがInt型の最大および最小境界を決定する方法を示しています。
main = do
print (maxBound :: Int)
print (minBound :: Int)
それは次の出力を生成します-
sh-4.3$ main
9223372036854775807
-9223372036854775808
次に、Char、Float、およびBool型の最大および最小境界を見つけてみてください。
Num
この型クラスは数値演算に使用されます。 Int、Integer、Float、Doubleなどの型は、このTypeクラスの下にあります。 次のコードを見てください-
main = do
print(2 :: Int)
print(2 :: Float)
それは次の出力を生成します-
sh-4.3$ main
2
2.0
積分
*Integral* は、Num Type Classのサブクラスと見なすことができます。 Num Typeクラスはすべてのタイプの数値を保持しますが、Integralタイプクラスは整数のみに使用されます。 IntおよびIntegerは、このTypeクラスの下のタイプです。
フローティング
Integralと同様に、FloatingもNum Typeクラスの一部ですが、浮動小数点数のみを保持します。 したがって、 Float と Double はこの型クラスの下にあります。
カスタムタイプクラス
他のプログラミング言語と同様に、Haskellでは開発者がユーザー定義型を定義できます。 次の例では、ユーザー定義型を作成して使用します。
data Area = Circle Float Float Float
surface :: Area -> Float
surface (Circle _ _ r) = pi * r ^ 2
main = print (surface $ Circle 10 20 10 )
ここでは、 Area という新しいタイプを作成しました。 次に、このタイプを使用して円の面積を計算しています。 上記の例では、「surface」は入力として Area を取り、出力として Float を生成する関数です。
ここで「データ」はキーワードであり、Haskellのすべてのユーザー定義型は常に大文字で始まることに注意してください。
それは次の出力を生成します-
sh-4.3$ main
314.15927
Haskell-関数
Haskellは関数型プログラミング言語であるため、関数は主要な役割を果たします。 他の言語と同様に、Haskellには独自の機能定義と宣言があります。
- 関数宣言は、関数名と引数リストとその出力で構成されます。
- 関数定義は、実際に関数を定義する場所です。
この概念を詳細に理解するために、 add 関数の小さな例を取り上げましょう。
add :: Integer -> Integer -> Integer --function declaration
add x y = x + y --function definition
main = do
putStrLn "The addition of the two numbers is:"
print(add 2 5) --calling a function
ここでは、1行目で関数を宣言し、2行目で2つの引数を取り、1つの整数型の出力を生成する実際の関数を記述しました。
他のほとんどの言語と同様に、Haskellは main メソッドからコードのコンパイルを開始します。 私たちのコードは次の出力を生成します-
The addition of the two numbers is:
7
パターンマッチング
パターンマッチングは、特定の種類の式を照合するプロセスです。 これは、コードを単純化するためのテクニックにすぎません。 この手法は、任意のタイプのTypeクラスに実装できます。 If-Elseは、パターンマッチングの代替オプションとして使用できます。
パターンマッチングは、実行時に引数リストに応じてさまざまなメソッドを実行できる動的なポリモーフィズムのバリアントと見なすことができます。
次のコードブロックを見てください。 ここでは、パターンマッチングの手法を使用して、数値の階乗を計算しました。
fact :: Int -> Int
fact 0 = 1
fact n = n * fact ( n - 1 )
main = do
putStrLn "The factorial of 5 is:"
print (fact 5)
数の階乗を計算する方法はすべて知っています。 コンパイラーは、引数を持つ「ファクト」と呼ばれる関数の検索を開始します。 引数が0に等しくない場合、数値は同じ関数を呼び出し続けますが、実際の引数よりも1少ない値になります。
引数のパターンが0と完全に一致すると、「ファクト0 = 1」であるパターンを呼び出します。 私たちのコードは次の出力を生成します-
The factorial of 5 is:
120
警備員
*Guards* は、パターンマッチングに非常によく似た概念です。 パターンマッチングでは、通常1つ以上の式を照合しますが、 *guards* を使用して式の一部のプロパティをテストします。
*guards* よりもパターンマッチングを使用することをお勧めしますが、開発者の観点からは、 *guards* はより読みやすく簡単です。 初めてのユーザーの場合、 *guards* はIf-Elseステートメントと非常によく似ていますが、機能的に異なります。
次のコードでは、 guards の概念を使用して factorial プログラムを変更しました。
fact :: Integer -> Integer
fact n | n == 0 = 1
| n/= 0 = n * fact (n-1)
main = do
putStrLn "The factorial of 5 is:"
print (fact 5)
ここでは、「|」で区切られた2つの*ガード*を宣言しましたそして main から fact 関数を呼び出します。 内部的には、コンパイラはパターンマッチングの場合と同じように動作して、次の出力を生成します-
The factorial of 5 is:
120
Where句
*Where* は、実行時に目的の出力を生成するために使用できるキーワードまたは組み込み関数です。 関数の計算が複雑になる場合に非常に役立ちます。
入力が複数のパラメーターを持つ複雑な式であるシナリオを考えます。 そのような場合、「where」節を使用して式全体を小さな部分に分割できます。
次の例では、複雑な数式を使用しています。 Haskellを使用して多項式方程式[x ^ 2-8x + 6]の根を見つける方法を示します。
roots :: (Float, Float, Float) -> (Float, Float)
roots (a,b,c) = (x1, x2) where
x1 = e + sqrt d/(2 *a)
x2 = e - sqrt d/(2* a)
d = b *b - 4* a *c
e = - b/(2* a)
main = do
putStrLn "The roots of our Polynomial equation are:"
print (roots(1,-8,6))
与えられた多項式関数の根を計算する式の複雑さに注意してください。 かなり複雑です。 したがって、 where 句を使用して式を壊しています。 上記のコードは次の出力を生成します-
The roots of our Polynomial equation are:
(7.1622777,0.8377223)
再帰関数
再帰は、関数がそれ自体を繰り返し呼び出す状況です。 Haskellは、式を複数回ループする機能を提供しません。 代わりに、Haskellは、機能全体をさまざまな機能のコレクションに分割し、再帰手法を使用して機能を実装することを望んでいます。
数値の階乗を計算したパターンマッチングの例をもう一度考えてみましょう。 数値の階乗を見つけることは、再帰を使用する典型的なケースです。 ここで、「パターンマッチングは再帰とどのように違うのでしょうか?」これら2つの違いは、使用方法にあります。 再帰は関数呼び出しであるのに対して、パターンマッチングは端末制約の設定で機能します。
次の例では、パターンマッチングと再帰の両方を使用して、5の階乗を計算しています。
fact :: Int -> Int
fact 0 = 1
fact n = n * fact ( n - 1 )
main = do
putStrLn "The factorial of 5 is:"
print (fact 5)
それは次の出力を生成します-
The factorial of 5 is:
120
高階関数
これまでに見てきたことは、Haskell関数が1つの type を入力として受け取り、別の type を出力として生成することです。これは、他の命令型言語とほとんど同じです。 高階関数は、関数を入力または出力引数として使用できるHaskellのユニークな機能です。
これは仮想概念ですが、実際のプログラムでは、Haskellで定義するすべての関数は高次メカニズムを使用して出力を提供します。 Haskellのライブラリ関数を調べる機会があれば、ほとんどのライブラリ関数がより高次の方法で記述されていることがわかります。
組み込みの高次関数マップをインポートし、それを使用して選択に応じて別の高次関数マップを実装する例を見てみましょう。
import Data.Char
import Prelude hiding (map)
map :: (a -> b) -> [a] -> [b]
map _ [] = []
map func (x : abc) = func x : map func abc
main = print $ map toUpper "finddevguides.com"
上記の例では、Type Class Char の toUpper 関数を使用して、入力を大文字に変換しました。 ここで、メソッド「map」は引数として関数を取り、必要な出力を返しています。 ここにその出力があります-
sh-4.3$ ghc -O2 --make *.hs -o main -threaded -rtsopts
sh-4.3$ main
"finddevguides.COM"
ラムダ式
アプリケーションの寿命全体を通して、一度だけ使用される関数を作成する必要がある場合があります。 このような状況に対処するために、Haskell開発者は lambda expression または lambda function として知られる別の匿名ブロックを使用します。
定義のない関数は、ラムダ関数と呼ばれます。 ラムダ関数は、「\」文字で示されます。 関数を作成せずに入力値を1増やす次の例を見てみましょう。
main = do
putStrLn "The successor of 4 is:"
print ((\x -> x + 1) 4)
ここでは、名前のない匿名関数を作成しました。 引数として整数4を取り、出力値を出力します。 基本的に、適切に宣言することなく、1つの関数を操作しています。 それがラムダ式の美しさです。
私たちのラムダ式は、次の出力を生成します-
sh-4.3$ main
The successor of 4 is:
5
Haskell-関数の詳細
ここまで、多くの種類のHaskell関数について説明し、それらの関数を呼び出すためのさまざまな方法を使用しました。 この章では、特別なTypeクラスをインポートせずにHaskellで簡単に使用できるいくつかの基本的な関数について学びます。 これらの関数のほとんどは、他の高次関数の一部です。
ヘッド機能
*Head* 関数はリストで機能します。 基本的にリストである入力引数の最初を返します。 次の例では、10個の値を持つリストを渡し、 *head* 関数を使用してそのリストの最初の要素を生成しています。
main = do
let x = [1..10]
putStrLn "Our list is:"
print (x)
putStrLn "The first element of the list is:"
print (head x)
それは次の出力を生成します-
Our list is:
[1,2,3,4,5,6,7,8,9,10]
The first element of the list is:
1
テール機能
*Tail* は、 *head* 関数を補完する関数です。 入力として *list* を取り、先頭部分のないリスト全体を生成します。 つまり、 *tail* 関数は、最初の要素なしでリスト全体を返します。 次の例を見てください-
main = do
let x = [1..10]
putStrLn "Our list is:"
print (x)
putStrLn "The tail of our list is:"
print (tail x)
それは次の出力を生成します-
Our list is:
[1,2,3,4,5,6,7,8,9,10]
The tail of our list is:
[2,3,4,5,6,7,8,9,10]
最後の機能
名前が示すように、入力として提供されるリストの最後の要素を生成します。 次の例を確認してください。
main = do
let x = [1..10]
putStrLn "Our list is:"
print (x)
putStrLn "The last element of our list is:"
print (last x)
それは次の出力を生成します-
Our list is:
[1,2,3,4,5,6,7,8,9,10]
The last element of our list is:
10
初期化関数
*Init* は、 *tail* 関数の反対の働きをします。 引数としてリストを取り、最後のエントリなしでリスト全体を返します。
main = do
let x = [1..10]
putStrLn "Our list is:"
print (x)
putStrLn "Our list without the last entry:"
print (init x)
今、その出力を観察します-
Our list is:
[1,2,3,4,5,6,7,8,9,10]
Our list without the last entry:
[1,2,3,4,5,6,7,8,9]
ヌル関数
*Null* は、指定されたリストが空の場合にのみ *True* を返し、それ以外の場合は *False* を返すブールチェック関数です。 次のコードは、指定されたリストが空かどうかを確認します。
main = do
let x = [1..10]
putStrLn "Our list is:"
print (x)
putStrLn "Is our list empty?"
print (null x)
それは次の出力を生成します-
Our list is:
[1,2,3,4,5,6,7,8,9,10]
Is our list empty?
False
逆機能
String入力で機能し、入力全体を逆順に変換し、結果として1つの出力を提供します。 以下は、この関数のコードベースです。
main = do
let x = [1..10]
putStrLn "Our list is:"
print (x)
putStrLn "The list in Reverse Order is:"
print (reverse x)
それは次の出力を生成します-
Our list is:
[1,2,3,4,5,6,7,8,9,10]
The list in Reverse Order is:
[10,9,8,7,6,5,4,3,2,1]
長さ関数
この関数は、引数として指定された list の長さを計算するために使用されます。 次の例を見てください-
main = do
let x = [1..10]
putStrLn "Our list is:"
print (x)
putStrLn "The length of this list is:"
print (length x)
リストには10個の要素があるため、出力として10のコードが生成されます。
Our list is:
[1,2,3,4,5,6,7,8,9,10]
The length of this list is:
10
機能を取る
*Take* 関数は、別のストリングからサブストリングを作成するために使用されます。 次のコードは、Haskellでtake関数を使用する方法を示しています-
main = print(take 5 ([1 .. 10]))
コードは、提供されたリストから5つの要素を含む部分文字列を生成します-
[1,2,3,4,5]
ドロップ機能
この関数は、部分文字列の生成にも使用されます。 take 関数の反対として機能します。 次のコードを見てください-
main = print(drop 5 ([1 .. 10]))
コードは、指定されたリストから最初の5つの要素を削除し、残りの5つの要素を出力します。 それは次の出力を生成します-
[6,7,8,9,10]
最大機能
この関数は、指定されたリストから最大値を持つ要素を見つけるために使用されます。 実際にそれを使用する方法を見てみましょう-
main = do
let x = [1,45,565,1245,02,2]
putStrLn "The maximum value element of the list is:"
print (maximum x)
上記のコードは次の出力を生成します-
The maximum value element of the list is:
1245
最小関数
この関数は、指定されたリストから最小値を持つ要素を見つけるために使用されます。 これは*最大*関数の反対です。
main = do
let x = [1,45,565,1245,02,2]
putStrLn "The minimum value element of the list is:"
print (minimum x)
上記のコードの出力は-
The minimum value element of the list is:
1
和関数
名前が示すように、この関数は指定されたリストに存在するすべての要素の合計を返します。 次のコードは、5つの要素のリストを受け取り、その合計を出力として返します。
main = do
let x = [1..5]
putStrLn "Our list is:"
print (x)
putStrLn "The summation of the list elements is:"
print (sum x)
それは次の出力を生成します-
Our list is:
[1,2,3,4,5]
The summation of the list elements is:
15
製品の機能
この関数を使用して、リスト内のすべての要素を乗算し、その値を出力できます。
main = do
let x = [1..5]
putStrLn "Our list is:"
print (x)
putStrLn "The multiplication of the list elements is:"
print (product x)
私たちのコードは次の出力を生成します-
Our list is:
[1,2,3,4,5]
The multiplication of the list elements is:
120
エレム関数
この関数は、指定されたリストに特定の要素が含まれているかどうかを確認するために使用されます。 したがって、 true または false を返します。
次のコードは、指定された要素のリストに値786が含まれているかどうかを確認します。
main = do
let x = [1,45,155,1785]
putStrLn "Our list is:"
print (x)
putStrLn "Does it contain 786?"
print (elem 786 (x))
それは次の出力を生成します-
Our list is:
[1,45,155,1785]
Does it contain 786?
False
同じコードを使用して、提供されたリストに値1785が含まれているかどうかを確認します。
Haskell-関数の構成
関数構成*は、ある関数の出力を別の関数の入力として使用するプロセスです。 *組成*の背後にある数学を学べばもっと良いでしょう。 数学では、 *composition は f \ {g(x)} で示されます。ここで、* g()は関数であり、その出力は別の関数の入力、つまり f()*です。
関数合成は、1つの関数の出力タイプが2番目の関数の入力タイプと一致する場合、任意の2つの関数を使用して実装できます。 Haskellで関数構成を実装するには、ドット演算子(。)を使用します。
次のサンプルコードをご覧ください。 ここでは、関数合成を使用して、入力数が偶数か奇数かを計算しました。
eveno :: Int -> Bool
noto :: Bool -> String
eveno x = if x `rem` 2 == 0
then True
else False
noto x = if x == True
then "This is an even Number"
else "This is an ODD number"
main = do
putStrLn "Example of Haskell Function composition"
print ((noto.eveno)(16))
ここで、 main 関数では、 noto と eveno の2つの関数を同時に呼び出しています。 コンパイラーは、最初に 16 を引数として関数 "eveno()" を呼び出します。 その後、コンパイラは eveno メソッドの出力を* noto()*メソッドの入力として使用します。
その出力は次のようになります-
Example of Haskell Function composition
"This is an even Number"
入力として16(偶数)を指定しているため、* eveno()関数は *true を返し、これは* noto()*関数の入力になり、出力を返します。「これは偶数」。
Haskell-モジュール
Javaで作業したことがある場合は、 package というフォルダーにすべてのクラスがどのようにバインドされているかがわかります。 同様に、Haskellは*モジュール*のコレクションと考えることができます。
Haskellは関数型言語であり、すべてが式として示されているため、モジュールは類似または関連するタイプの関数のコレクションとして呼び出すことができます。
1つのモジュールから別のモジュールに関数を*インポート*できます。 他の関数の定義を開始する前に、すべての「import」ステートメントが最初に来るはずです。 この章では、Haskellモジュールのさまざまな機能について学びます。
リストモジュール
*List* は、 *list* 型のデータを処理するための素晴らしい機能を提供します。 リストモジュールをインポートすると、さまざまな機能を自由に使用できます。
次の例では、Listモジュールで使用可能ないくつかの重要な機能を使用しています。
import Data.List
main = do
putStrLn("Different methods of List Module")
print(intersperse '.' "finddevguides.com")
print(intercalate " " ["Lets","Start","with","Haskell"])
print(splitAt 7 "HaskellTutorial")
print (sort [8,5,3,2,1,6,4,2])
ここでは、多くの関数を定義せずに使用しています。 これは、これらの関数がリストモジュールで使用できるためです。 Listモジュールをインポートした後、Haskellコンパイラーはこれらすべての関数をグローバル名前空間で利用可能にしました。 したがって、これらの関数を使用できます。
私たちのコードは次の出力を生成します-
Different methods of List Module
"T.u.t.o.r.i.a.l.s.p.o.i.n.t...c.o.m"
"Lets Start with Haskell"
("Haskell","Tutorial")
[1,2,2,3,4,5,6,8]
チャーモジュール
*Char* モジュールには、Characterタイプで動作する多くの事前定義関数があります。 次のコードブロックを見てください-
import Data.Char
main = do
putStrLn("Different methods of Char Module")
print(toUpper 'a')
print(words "Let us study tonight")
print(toLower 'A')
ここで、関数 toUpper および toLower は Char モジュール内で既に定義されています。 それは次の出力を生成します-
Different methods of Char Module
'A'
["Let","us","study","tonight"]
'a'
マップモジュール
*Map* は、並べ替えられていない付加価値ペア型のデータ型です。 多くの便利な機能を備えた広く使用されているモジュールです。 次の例は、Mapモジュールで使用可能な事前定義済み関数の使用方法を示しています。
import Data.Map (Map)
import qualified Data.Map as Map --required for GHCI
myMap :: Integer -> Map Integer [Integer]
myMap n = Map.fromList (map makePair [1..n])
where makePair x = (x, [x])
main = print(myMap 3)
それは次の出力を生成します-
fromList [(1,[1]),(2,[2]),(3,[3])]
セットモジュール
Setモジュールには、数学データを操作するための非常に便利な定義済み関数がいくつかあります。 セットはバイナリツリーとして実装されるため、セット内のすべての要素は一意である必要があります。
次のサンプルコードを見てください
import qualified Data.Set as Set
text1 = "Hey buddy"
text2 = "This tutorial is for Haskell"
main = do
let set1 = Set.fromList text1
set2 = Set.fromList text2
print(set1)
print(set2)
ここでは、StringをSetに変更しています。 次の出力が生成されます。 出力セットに文字の繰り返しがないことを確認してください。
fromList " Hbdeuy"
fromList " HTaefhiklorstu"
カスタムモジュール
他のプログラムで呼び出すことができるカスタムモジュールを作成する方法を見てみましょう。 このカスタムモジュールを実装するには、 "main.hs" とともに "custom.hs" という別のファイルを作成します。
カスタムモジュールを作成し、その中にいくつかの関数を定義しましょう。
custom.hs
module Custom (
showEven,
showBoolean
) where
showEven:: Int-> Bool
showEven x = do
if x 'rem' 2 == 0
then True
else False
showBoolean :: Bool->Int
showBoolean c = do
if c == True
then 1
else 0
カスタムモジュールの準備ができました。 次に、プログラムにインポートします。
main.hs
import Custom
main = do
print(showEven 4)
print(showBoolean True)
私たちのコードは次の出力を生成します-
True
1
「4」は偶数なので、 showEven 関数は True を返します。 showBoolean 関数は、関数に渡したブール関数が「True」であるため、「1」を返します。
Haskell-入力および出力
これまでに説明したすべての例は、本質的に静的です。 この章では、ユーザーと動的に通信する方法を学びます。 Haskellで使用されるさまざまな入力および出力テクニックを学びます。
ファイルとストリーム
これまで、プログラム自体のすべての入力をハードコーディングしました。 静的変数から入力を取得しています。 次に、外部ファイルからの読み取りおよび書き込みの方法を学びましょう。
ファイルを作成して、「abc.txt」という名前を付けましょう。 次に、このテキストファイルに「Welcome to finddevguides。 ここでは、Haskellを学ぶのに最適なリソースを入手できます。」
次に、このファイルの内容をコンソールに表示する次のコードを記述します。 ここでは、EOF文字が見つかるまでファイルを読み取る関数readFile()を使用しています。
main = do
let file = "abc.txt"
contents <- readFile file
putStrLn contents
上記のコードは、「abc.txt」というファイルを文字列として読み取り、ファイルの終わり文字が見つかるまで読み取ります。 このコードは次の出力を生成します。
Welcome to finddevguides
Here, you will get the best resource to learn Haskell.
端末で印刷しているものはすべて、そのファイルに書き込まれていることを確認します。
コマンドライン引数
Haskellは、コマンドプロンプトからファイルを操作する機能も提供します。 ターミナルに戻り、 "ghci" と入力しましょう。 次に、コマンドの次のセットを入力します-
let file = "abc.txt"
writeFile file "I am just experimenting here."
readFile file
ここでは、「abc.txt」というテキストファイルを作成しました。 次に、コマンド writeFile を使用して、ファイルにステートメントを挿入しました。 最後に、コマンド readFile を使用して、コンソールにファイルの内容を印刷しました。 私たちのコードは次の出力を生成します-
I am just experimenting here.
例外
- 例外*はコードのバグとみなすことができます。 これは、コンパイラーが実行時に期待される出力を取得しない状況です。 他の優れたプログラミング言語と同様に、Haskellは例外処理を実装する方法を提供します。
Javaに精通している場合は、Try-Catchブロックを知っているかもしれません。通常はエラーをスローし、 catch ブロックで同じエラーをキャッチします。 Haskellには、ランタイムエラーをキャッチするための同じ関数もあります。
- try の関数定義は、「try
- Exception e ⇒ IO a→ IO(e e e a)」のようになります。 次のサンプルコードをご覧ください。 「ゼロ除算」例外をキャッチする方法を示しています。
import Control.Exception
main = do
result <- try (evaluate (5 `div` 0)) :: IO (Either SomeException Int)
case result of
Left ex -> putStrLn $ "Caught exception: " ++ show ex
Right val -> putStrLn $ "The answer was: " ++ show val
上記の例では、 Control.Exception モジュールの組み込みの try 関数を使用しているため、事前に例外をキャッチしています。 上記のコードは、画面に以下の出力を生成します。
Caught exception: divide by zero
ハスケル-ファンクター
Haskellの Functor は、さまざまなタイプの機能表現の一種であり、マッピングできます。 これは、ポリモーフィズムを実装するための高レベルの概念です。 Haskell開発者によると、リスト、マップ、ツリーなどのすべてのタイプ Haskell Functorのインスタンスです。
- ファンクター*は、次のような関数定義を持つ組み込みクラスです-
class Functor f where
fmap :: (a -> b) -> f a -> f b
この定義により、 Functor は関数、たとえば* fmap()を取り、別の関数を返す関数であると結論付けることができます。 上記の例では、 fmap()は関数 map()*の一般化された表現です。
次の例では、Haskell Functorの動作を確認します。
main = do
print(map (subtract 1) [2,4,8,16])
print(fmap (subtract 1) [2,4,8,16])
ここでは、減算演算のリストで* map()と fmap()*の両方を使用しています。 両方のステートメントが、要素[1,3,7,15]を含むリストの同じ結果を生成することがわかります。
両方の関数が* subtract()*と呼ばれる別の関数を呼び出して結果を生成しました。
[1,3,7,15]
[1,3,7,15]
次に、 map と fmap の違いは何ですか?違いはそれらの使用法にあります。 Functor を使用すると、「just」や「Nothing」など、さまざまなデータ型の機能主義者を実装できます。
main = do
print (fmap (+7)(Just 10))
print (fmap (+7) Nothing)
上記のコードは、端末に次の出力を生成します-
Just 17
Nothing
Applicative Functor
Applicative Functorは、Applicative Type Classによって提供されるいくつかの追加機能を備えた通常のFunctorです。
Functorを使用して、通常、既存の関数とその内部で定義された別の関数をマッピングします。 ただし、Functor内で定義された関数を別のFunctorにマップする方法はありません。 そのため、 Applicative Functor という別の機能があります。 このマッピング機能は、 Control モジュールの下で定義されたApplicative Typeクラスによって実装されます。 このクラスでは、2つのメソッドのみを使用できます。1つは pure で、もう1つは* <*> *です。
以下は、Applicative Functorのクラス定義です。
class (Functor f) => Applicative f where
pure :: a -> f a
(<*>) :: f (a -> b) -> f a -> f b
実装によると、2つのメソッド "Pure" および* "<*>" *を使用して別のFunctorをマップできます。 「Pure」メソッドは任意のタイプの値を取る必要があり、常にその値のApplicative Functorを返します。
次の例は、Applicative Functorの仕組みを示しています-
import Control.Applicative
f1:: Int -> Int -> Int
f1 x y = 2*x+y
main = do
print(show $ f1 <$> (Just 1) <*> (Just 2) )
ここでは、関数 f1 の関数呼び出しに適用可能なファンクターを実装しました。 私たちのプログラムは次の出力を生成します。
"Just 4"
モノイド
Haskellがすべてを関数の形式で定義していることを知っています。 関数には、関数の出力として入力を取得するオプションがあります。 これが Monoid です。
*Monoid* は、出力が入力から独立している関数と演算子のセットです。 関数(*)と整数(1)を取りましょう。 これで、入力に関係なく、その出力は同じ数値のみになります。 つまり、数値に1を掛けると、同じ数値が得られます。
モノイドのタイプクラスの定義を次に示します。
class Monoid m where
mempty :: m
mappend :: m -> m -> m
mconcat :: [m] -> m
mconcat = foldr mappend mempty
HaskellでのMonoidの使用を理解するには、次の例をご覧ください。
multi:: Int->Int
multi x = x * 1
add :: Int->Int
add x = x + 0
main = do
print(multi 9)
print (add 7)
私たちのコードは次の出力を生成します-
9
7
ここで、関数「multi」は入力に「1」を乗算します。 同様に、関数「add」は入力に「0」を追加します。 どちらの場合も、出力は入力と同じになります。 したがって、関数* \ {()、1} *および *\ {(+)、0} はモノイドの完全な例です。
ハスケル-モナド
*Monads* は、いくつかの追加機能を備えたApplicative Functorの一種です。 これは、*モナド規則*と呼ばれる3つの基本的な規則を管理するTypeクラスです。
3つのルールはすべて、次のようなモナド宣言に厳密に適用されます-
class Monad m where
return :: a -> m a
(>>=) :: m a -> (a -> m b) -> m b
(>>) :: m a -> m b -> m b
x >> y = x >>= \_ -> y
fail :: String -> m a
fail msg = error msg
モナド宣言に適用される3つの基本的な法則は次のとおりです-
- 左アイデンティティ法- return 関数は値を変更せず、Monadの何も変更しないでください。 「return> ⇒ mf = mf」と表現できます。
- Right Identity Law - return 関数は値を変更せず、Monadの何も変更すべきではありません。 「mf> ⇒ return = mf」と表現できます。
- 関連性-この法律によれば、ファンクターとモナドのインスタンスは同じように動作するはずです。 「(f> =⇒ g)> ⇒ h = f> ⇒(g> = h)」と数学的に表現できます。
最初の2つの法則は同じポイントを反復します。つまり、 return は bind 演算子の両側で恒等的な振る舞いを持つべきです。
モナドであることを認識せずに、前の例ですでに多くのモナドを使用しています。 List Monadを使用して特定のリストを生成する次の例を考えてみましょう。
main = do
print([1..10] >>= (\x -> if odd x then [x*2] else []))
このコードは、次の出力を生成します-
[2,6,10,14,18]
Haskell-ジッパー
Haskellの Zippers は、基本的に tree などのデータ構造の特定の場所を指すポインターです。
完全な二分木として表現できる5つの要素 [45,7,55,120,56] を持つ tree を考えてみましょう。 このリストの最後の要素を更新する場合は、すべての要素を走査して、更新する前に最後の要素に到達する必要があります。 右?
しかし、 N 要素を持つツリーが [(N-1)、N] のコレクションであるような方法でツリーを構築できたらどうでしょう。 次に、不要な*(N-1)*要素をすべて走査する必要はありません。 N番目の要素を直接更新できます。 これがまさにZipperのコンセプトです。 ツリー全体を走査せずにその値を更新できるツリーの特定の場所にフォーカスまたはポイントします。
次の例では、リストにZipperの概念を実装しています。 同様に、*ツリー*または*ファイル*データ構造にZipperを実装できます。
data List a = Empty | Cons a (List a) deriving (Show, Read, Eq, Ord)
type Zipper_List a = ([a],[a])
go_Forward :: Zipper_List a -> Zipper_List a
go_Forward (x:xs, bs) = (xs, x:bs)
go_Back :: Zipper_List a -> Zipper_List a
go_Back (xs, b:bs) = (b:xs, bs)
main = do
let list_Ex = [1,2,3,4]
print(go_Forward (list_Ex,[]))
print(go_Back([4],[3,2,1]))
上記のプログラムをコンパイルして実行すると、次の出力が生成されます-
([2,3,4],[1])
([3,4],[2,1])
ここでは、前方または後方に向かって、弦全体の要素に注目しています。