ハスケルぞのずげを通しお。 1/2



Haskellの短くお難しい玹介の最初の郚分。 2番目の郚分はここにありたす。

tl; dr Haskellの非垞に短く簡朔な玹介。


UPD。 チュヌトリアルが気に入ったら、 元の蚘事の著者に数行をドロップしおください 。 人は喜んでいたす;

私はすべおの開発者がHaskellを孊ぶべきだず本圓に信じおいたす。 誰もがスヌパヌハスケル忍者になるべきではないず思いたす。 人々は、Haskellが提䟛する機䌚に泚意する必芁がありたす。 Haskellを孊習するず、意識が広がりたす。


䞻流の蚀語は非垞に䌌おいたす



Haskellはそれらずは倧きく異なりたす。 この蚀語は、これたで聞いたこずがない抂念の束を䜿甚しおいたす。 これらの抂念の倚くは、より優れたプログラマヌになるのに圹立ちたす。


しかし、Haskellの孊習は難しい堎合がありたす。 少なくずも私にずっおは難しいこずでした。 そしおこの蚘事では、勉匷の過皋で欠けおいたものを芋せようずしたす。

この蚘事をマスタヌするのは難しいでしょう。 これは意図的に行われたした。 Haskellを孊ぶ簡単な方法はありたせん。 この道は難しくお難しいです。 しかし、それは良いこずだず思いたす。 興味深いのは、Haskellの耇雑さだからです。

Haskellを習埗する通垞の方法は、2冊の本を読むこずです。
最初に「Learn You a Haskell」 、次に「Real World Haskell」 。

これが孊習ぞの正しいアプロヌチであるこずに同意したす。 しかし、Haskellが䜕であるかを理解するには、これらの本を非垞に泚意深く読む必芁がありたす。


䞀方、この蚘事はHaskellのすべおの基本的な偎面を非垞に簡朔か぀簡朔に玹介しおいたす。 Haskellを勉匷したずきに欠けおいたニュアンスもいく぀か远加したした。


この蚘事は5぀のパヌトで構成されおいたす。



泚ファむル名ず拡匵子が.lhs区切り文字が衚瀺される.lhs 、
ダりンロヌドできたす。
ファむルをfilename.lhsずしお保存するず、次のfilename.lhsで実行できたす。
runhaskell filename.lhs


いく぀かの䟋は動䜜しないかもしれたせんが、ほずんどは動䜜するはずです。
以䞋に、ファむルぞのリンクが衚瀺されたす




01_basic / 10_Introduction / 00_hello_world.lhs


はじめに



蚭眮







ナヌティリティ




パニックなし





倚くの本や蚘事は、いく぀かの難解な公匏クむック゜ヌト、フィボナッチなどでHaskellを知っおいたす。 私は異なった行動をしたす。 Haskellの超倧囜をすぐには芋せたせん。 Haskellが他のプログラミング蚀語に䌌おいるものに集䞭したす。 それでは、おなじみの「Hello World」から始めたしょう。


 main = putStrLn "Hello World!" 


実行するには、このコヌドをhello.hsずしお保存し、 hello.hsを実行したす。

  ~ runhaskell ./hello.hs Hello World! 


「はじめに」の䞋にあるリンクから゜ヌスをダりンロヌドできたす

ファむル00_hello_world.lhsを保存しお実行したす。

  ~ runhaskell 00_hello_world.lhs Hello World! 


01_basic / 10_Introduction / 00_hello_world.lhs



01_basic / 10_Introduction / 10_hello_you.lhs

次に、名前を尋ねお「こんにちは、名前」ず蚀うプログラムを䜜成したす。

 main = do print "  ?" name <- getLine print (" " ++ name ++ "!") 


たず、他の呜什型蚀語ず䟋を比范しおください。

 # Python print "  ?" name = raw_input() print " %s!" % name 


 # Ruby puts "  ?" name = gets.chomp puts " #{name}!" 


 // In C #include <stdio.h> int main (int argc, char **argv) { char name[666]; // <-  ! //  ,     665 ? printf("  ?\n"); scanf("%s", name); printf(" %s!\n", name); return 0; } 


プログラムの構造は非垞に䌌おいたすが、わずかな構文䞊の違いがありたす。 チュヌトリアルの䞻芁郚分では、これらの違いを正確に説明したす。


Haskellでは、各関数ずオブゞェクトには独自の型がありたす。
main関数のタむプはIO ()です。
これは、実行時にmainが副䜜甚を匕き起こすこずを意味したす。

しかし、Haskellが通垞の呜什型蚀語のように芋えるこずを忘れないでください。

01_basic / 10_Introduction / 10_hello_you.lhs



01_basic / 10_Introduction / 20_very_basic.lhs


最も基本的なHaskell





続行する前に、Haskellの特別な機胜のいく぀かに぀いお譊告したいず思いたす。

機胜性

Haskellは関数型蚀語です。
呜什型蚀語で始めた堎合、倚くの新しいこずを孊ぶ必芁がありたす。
幞いなこずに、これらの抂念の倚くは、呜什型蚀語でもプログラムの改善に圹立ちたす。

高床な静的型付け

CやC++ 、 Javaように邪魔する代わりに、型システムが圹立ちたす。

枅朔さ

䞀般的に蚀えば、あなたの機胜は倖の䞖界では䜕も倉えたせん。
䞀方で、これは、関数が倉数の倀を倉曎できず、ナヌザヌからデヌタを受信できず、画面に曞き蟌むこずができず、ロケットを発射できないこずを意味したす。
䞀方、プログラムの䞊列化は簡単です。
Haskellは、玔粋な関数ず副䜜甚を匕き起こす関数ずの間に明確な線を匕きたす。
このアプロヌチにより、プログラムのプロパティを分析するのがはるかに簡単になりたす。
プログラムの機胜的にきれいな郚分では、コンパむル段階で倚くの゚ラヌが陀倖されたす。


さらに、機胜的に玔粋な関数は、基本的なHaskellの法則に埓いたす。

同じパラメヌタヌを䜿甚した関数呌び出しは、垞に同じ結果をもたらしたす。


怠azine

デフォルトの遅延は、プログラミング蚀語では非垞に珍しいこずです。
デフォルトでは、Haskellは本圓に必芁な堎合にのみ倀を評䟡したす。
その結果、無限のデヌタ構造を非垞に簡単に扱うこずができたす。

最埌の譊告は、Haskellのコヌドを読む䟡倀があるかどうかに関係したす。
私にずっお、これは科孊論文を読むようなものです。
䞀郚の郚分は単玔明快かもしれたせんが、匏が芋られる堎合は、集䞭しお読んでください。
Haskellを孊習するずき、構文の機胜の䞀郚を完党に理解しおいなくおも問題はありたせん。
>>= 、 <$> 、 <-などの恐ろしい文字を思い぀いた堎合は、それらを無芖しお、コヌドの䞀般的な考え方を理解しおください。


関数定矩



次のように関数を定矩できたす。

C 
 int f(int x, int y) { return x*x + y*y; } 


Javascriptの堎合
 function f(x,y) { return x*x + y*y; } 


Pythonの堎合
 def f(x,y): return x*x + y*y 


Rubyの堎合
 def f(x,y) x*x + y*y end 


スキヌムに぀いお
 (define (fxy) (+ (* xx) (* yy))) 


そしお最埌に、Haskell
 fxy = x*x + y*y 


非垞に明確です。 括匧なし、 defなし。

Haskellは関数ず型を倚甚しおいるこずに泚意しおください。
したがっお、それらは非垞に簡単に識別できたす。
蚀語の構文は、定矩をできるだけ単玔にするために考案されたした。


新しいタむプを䜜成する䟋



通垞、プログラムを䜜成するずきに、関数のタむプを指定したす。
しかし、これは必芁ありたせん。
コンパむラヌは、これを行うのに十分スマヌトです。

少し遊びたしょう。
 --     :: f :: Int -> Int -> Int fxy = x*x + y*y main = print (f 2 3) 


 ~ runhaskell 20_very_basic.lhs 13 

01_basic / 10_Introduction / 20_very_basic.lhs



01_basic / 10_Introduction / 21_very_basic.lhs

今すぐ詊しおください

 f :: Int -> Int -> Int fxy = x*x + y*y main = print (f 2.3 4.2) 


゚ラヌが衚瀺されたす
 21_very_basic.lhs:6:23: No instance for (Fractional Int) arising from the literal `4.2' Possible fix: add an instance declaration for (Fractional Int) In the second argument of `f', namely `4.2' In the first argument of `print', namely `(f 2.3 4.2)' In the expression: print (f 2.3 4.2) 


問題は4.2がIntではないこずです。

01_basic / 10_Introduction / 21_very_basic.lhs



01_basic / 10_Introduction / 22_very_basic.lhs

1぀の解決策は、関数fタむプを明瀺的に指定しないこずです。
次に、Haskellが最も䞀般的な適切なタむプを掚枬したす。

 fxy = x*x + y*y main = print (f 2.3 4.2) 


皌いだ
玠晎らしい、各デヌタ型に新しい関数を䜜成する必芁はありたせん。
たずえば、 Cでは、 int 、 float 、 long 、 doubleなどの関数を䜜成する必芁がありたす。

しかし、どのタむプを瀺す必芁がありたすか
Haskellがどのタむプを掚枬したかを理解するには、単にghciを実行したす

 % ghci GHCi, version 7.0.4: http://www.haskell.org/ghc/ :? for help Loading package ghc-prim ... linking ... done. Loading package integer-gmp ... linking ... done. Loading package base ... linking ... done. Loading package ffi-1.0 ... linking ... done. Prelude> let fxy = x*x + y*y Prelude> :type f f :: Num a => a -> a -> a 


うヌん...これはどんな奇劙なタむプですか

 Num a => a -> a -> a 


たず、a- a -> a -> a右偎に泚意しおください。
それを理解するために、いく぀かの䟋を芋おみたしょう。

指定されたタむプ䟡倀
IntタむプInt
Int -> Int入力Intを受け取り、 Intを返す関数
Float -> IntFloatを受け入れ、 Intを返す関数
a -> Int入力ずしお任意の型を取り、 Intを返す関数の型
a -> a入力タむプaを受け取り、タむプa結果を返す関数のタむプ
a -> a -> a入力ずしおタむプa 2぀の匕数を取り、タむプaの結果を返す関数のタむプ


タむプa -> a -> aでは、文字aはタむプ倉数です 。
぀たり、 fは2぀の匕数の関数であり、䞡方の匕数ず結果は同じ型です。
タむプa倉数は、さたざたなタむプの倀を取るこずができたす。
たずえば、 Int 、 Integer 、 Float ...

したがっお、 Cように型をハヌドに蚭定する代わりに、 int 、 long 、 float 、 doubleなどの関数を個別に定矩したす...
動的に型付けされた蚀語のように、1぀の関数を䜜成するだけです。

䞀般的に、 aはどのタむプでもかたいたせん。
Trees 、別の関数型などのより耇雑な型のString 、 Int 。
しかし、この䟋では、型には接頭蟞Num a =>が付いおいたす。

Numは型クラスです。
型クラスは、倚くの型ずしお衚すこずができたす。
Numは、数倀のように動䜜する型のみが含たれたす。
より厳密には、 Numは特定の関数セット、特に(+)ず(*)を実装する型を含むクラスです。

型クラスは、蚀語の非垞に匷力な芁玠です。
圌らの助けを借りお、私たちは玠晎らしいこずをするこずができたす。
しかし、これに぀いおは少し埌で説明したす。

したがっお、 Num a => a -> a -> aず曞くずNum a => a -> a -> aこずを意味したす。

a Num型クラスに属する型にしたす。
これは、入力ずしおaを受け取り、a- a -> a を返す関数です。

うヌん、それは奇劙です。
実際、Haskellでは、2぀の匕数を入力ずしお受け入れる関数はありたせん。
代わりに、すべおの関数には匕数が1぀しかありたせん。
ただし、2぀の匕数の関数は、最初の匕数を取り、2番目の匕数をパラメヌタヌずしお取る関数ずしお返すこずができるこずに泚意しおください。
。

぀たり、 f 3 4 (f 3) 4 f 3 4同等です。
f 3も関数であるこずに泚意しおください。

 f :: Num a :: a -> a -> a g :: Num a :: a -> a g = f 3 gy ⇔ 3*3 + y*y 


別の゚ントリを䜿甚しお関数を䜜成するこずもできたす。
ラムダ匏を䜿甚するず、名前を付けずに関数を䜜成できたす。
これらは匿名関数ずも呌ばれたす。
私たちは曞くこずができたす

 g = \y -> 3*3 + y*y 


\文字は、 λ文字に䌌おいるため䜿甚されたすが、同時にASCII文字です。

関数型プログラミングが初めおの堎合、この時点で脳は沞隰し始めたす。
実際のアプリケヌションを䜜成したす。

01_basic / 10_Introduction / 22_very_basic.lhs



01_basic / 10_Introduction / 23_very_basic.lhs

ただし、開始する前に、型システムが正垞に機胜するこずを確認する必芁がありたす。

 f :: Num a => a -> a -> a fxy = x*x + y*y main = print (f 3 2.4) 


このコヌドは、 3がFloat型の小数小数ずInteger型の敎数の䞡方を衚すこずができるため機胜したす。
2.4は分数型であるため、 3も分数ずしお衚されたす。

01_basic / 10_Introduction / 23_very_basic.lhs



01_basic / 10_Introduction / 24_very_basic.lhs

関数に他の型を曞くず、機胜しなくなりたす

 f :: Num a => a -> a -> a fxy = x*x + y*y x :: Int x = 3 y :: Float y = 2.4 main = print (fxy) --   ,   x ≠  y 


コンパむラぱラヌを通知したす。
2぀のパラメヌタヌは同じタむプでなければなりたせん。

これが悪い考えであり、コンパむラヌが型を倉換する必芁があるず思う堎合、本圓に玠晎らしいビデオを芋る必芁がありたす。
ワット

01_basic / 10_Introduction / 24_very_basic.lhs


Haskellの最小芁件





このセクションをすぐに確認するこずをお勧めしたす。
それを参考資料ず考えおください。
Haskellは非垞に倚面的な蚀語です。
したがっお、このセクションのいく぀かのこずはスキップされたす。
必芁に応じお、䜕かがおかしい、たたは理解できないず思われる堎合は、ここに戻っおください。

シンボル⇔を䜿甚しお、2぀の匏の等䟡性を瀺したす。
これはメタ衚蚘です; Haskellにははありたせん。
蚘号⇒は、匏の評䟡結果を瀺すために䜿甚されたす。

衚珟



算術

 3 + 2 * 6 / 3 ⇔ 3 + ((2*6)/3) 


論理的

 True || False ⇒ True True && False ⇒ False True == False ⇒ False True /= False ⇒ True (/=)     


べき乗

 x^n   n (Int  Integer) x**y    y ( Float) 

Integerは、マシンのRAMを陀き、サむズの制限はありたせん。

 4^103 102844034832575377634685573909834406561420991602098741459288064 


そうそう
そしお、有理数を䜿甚できたす
ただし、このためにはData.Ratioモゞュヌルを䜿甚する必芁がありたす。

 $ ghci .... Prelude> :m Data.Ratio Data.Ratio> (11 % 15) * (5 % 3) 11 % 9 


リスト

 [] ⇔   [1,2,3] ⇔    ["foo","bar","baz"] ⇔   (String) 1:[2,3] ⇔ [1,2,3], (:)    1:2:[] ⇔ [1,2] [1,2] ++ [3,4] ⇔ [1,2,3,4], (++)   [1,2,3] ++ ["foo"] ⇔  String ≠ Integral [1..4] ⇔ [1,2,3,4] [1,3..10] ⇔ [1,3,5,7,9] [2,3,5,7,11..100] ⇔ !    ! [10,9..1] ⇔ [10,9,8,7,6,5,4,3,2,1] 


行


Haskellでは、文字列はCharリストです。
 'a' :: Char "a" :: [Char] "" ⇔ [] "ab" ⇔ ['a','b'] ⇔ 'a':"b" ⇔ 'a':['b'] ⇔ 'a':'b':[] "abc" ⇔ "ab"++"c" 

泚 
実際のタスクでは、文字のリストを䜿甚しおテキストを操䜜したせん。
ほずんどの堎合、 Data.Textこれに䜿甚されたす。
ASCII文字のストリヌムを操䜜する必芁がある堎合は、 Data.ByteStringを䜿甚する必芁がありたす。



タプル


次のようにペアを指定できたす(a,b) 。
タプルの芁玠にはさたざたなタむプがありたす。

 --    -  (2,"foo") (3,'a',[2,3]) ((2,"a"),"c",3) fst (x,y) ⇒ x snd (x,y) ⇒ y fst (x,y,z) ⇒ ERROR: fst :: (a,b) -> a snd (x,y,z) ⇒ ERROR: snd :: (a,b) -> b 



括匧を扱いたす


䜙分な角かっこを取り陀くには、これらの関数($)ず(.)䜿甚できたす。

 --  : fghx ⇔ (((fg) h) x) -- $    $ --    fg $ hx ⇔ fg (hx) ⇔ (fg) (hx) f $ ghx ⇔ f (ghx) ⇔ f ((gh) x) f $ g $ hx ⇔ f (g (hx)) -- (.)   (f . g) x ⇔ f (gx) (f . g . h) x ⇔ f (g (hx)) 




01_basic / 20_Essential_Haskell / 10a_Functions.lhs


関数を曞くのに圹立぀こず



ちょっずした泚意
 x :: Int ⇔ x   Int x :: a ⇔ x     x :: Num a => a ⇔ x     a,     Num f :: a -> b ⇔ f      a     b f :: a -> b -> c ⇔ f  ,    a   (b→c) f :: (a -> b) -> c ⇔ f  ,    (a→b)   c 


定矩する前に関数型を宣蚀するのはオプションです。
Haskell自䜓が最も䞀般的なタむプを掚枬したす。
ただし、関数のタむプを指定するのは経隓則です。

䞭眮゚ントリ

 square :: Num a => a -> a square x = x^2 


^䞭眮蚘法で䜿甚されるこずに泚意しおください。
各䞭眮挔算子には、接頭蟞を曞く可胜性がありたす。
目的の挔算子を角かっこで囲むだけです。

 square' x = (^) x 2 square'' x = (^2) x 


匏の巊偎ず右偎からxを削陀できたす
これは、η削枛ず呌ばれたす。
 square''' = (^2) 

関数名に'文字を䜿甚できるこずに泚意しおください。
䟋
square ⇔ square' ⇔ square'' ⇔ square '''


テスト

 absolute :: (Ord a, Num a) => a -> a absolute x = if x >= 0 then x else -x 


泚Haskellのif .. then .. elseは、挔算子に䌌おいたす
€?€:€ C. elseは省略できたせelse 。

他の同等の機胜

 absolute' x | x >= 0 = x | otherwise = -x 

泚Haskellプログラムではアラむメントが重芁です。
Pythonの堎合ず同様に、ミスアラむメントはコヌドを砎壊する可胜性がありたす



難しい郚分



耇雑な郚分の研究に進みたす。

機胜的なスタむル



このセクションでは、Haskellの驚くべきコヌドリファクタリング機胜の小さな䟋を玹介したす。
問題を遞択し、暙準の呜什的な方法で解決したす。 次に、このコヌドを少し改善し始めたす。 最終結果は、はるかに理解しやすく、より゚レガントになりたす。


解決する問題の状態は次のずおりです。

敎数のリストがある堎合、リスト内の偶数の合蚈を蚈算する必芁がありたす。

䟋
[1,2,3,4,5] ⇒ 2 + 4 ⇒ 6


機胜的アプロヌチず呜什型アプロヌチの違いを瀺すために、最初に呜什型゜リュヌションをJavascriptで蚘述したす。

 function evenSum(list) { var result = 0; for (var i=0; i< list.length ; i++) { if (list[i] % 2 ==0) { result += list[i]; } } return result; } 

しかし、Haskellには倉数ずルヌプはありたせん。
ルヌプを䜿甚しなくおも、再垰を䜿甚しお同じ結果を埗るこずができたす。

泚 
呜什型蚀語の再垰は、遅いツヌルであるずいう評刀がありたす。 しかし、ほずんどの機胜蚀語ではそうではありたせん。 ほずんどの堎合、Haskellは非垞に高品質で再垰関数を䜿甚しお䜜業を最適化したす。


C蚘述された再垰関数のバヌゞョンを次に瀺したすC 簡単にするために、数字のリストがヌル倀で終わるず仮定したす。
 int evenSum(int *list) { return accumSum(0,list); } int accumSum(int n, int *list) { int x; int *xs; if (*list == 0) { // if the list is empty return n; } else { x = list[0]; // let x be the first element of the list xs = list+1; // let xs be the list without x if ( 0 == (x%2) ) { // if x is even return accumSum(n+x, xs); } else { return accumSum(n, xs); } } } 

このコヌドをよく芋おください。 Haskellに翻蚳するからです。
しかし、最初に、䜿甚する3぀のシンプルだが非垞に䟿利な関数を玹介したす。

 even :: Integral a => a -> Bool head :: [a] -> a tail :: [a] -> [a] 


evenパリティチェック。
 even :: Integral a => a -> Bool even 3 ⇒ False even 2 ⇒ True 

headはリストの最初の芁玠を返したす
 head :: [a] -> a head [1,2,3] ⇒ 1 head [] ⇒ ERROR 


tailは、最初のリストに加えお、リストのすべおの芁玠を返したす。
 tail :: [a] -> [a] tail [1,2,3] ⇒ [2,3] tail [3] ⇒ [] tail [] ⇒ ERROR 

空でないリストl 、
l ⇔ (head l):(tail l)



02_Hard_Part / 11_Functions.lhs

だから、Haskellの問題に察する最初の解決策。
evenSum関数は、リスト内のすべおの偶数の合蚈を返したす。

 --  1 evenSum :: [Integer] -> Integer evenSum l = accumSum 0 l accumSum nl = if l == [] then n else let x = head l xs = tail l in if even x then accumSum (n+x) xs else accumSum n xs 


関数をテストするには、 ghci実行するだけghci 
 % ghci GHCi, version 7.0.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> :load 11_Functions.lhs [1 of 1] Compiling Main ( 11_Functions.lhs, interpreted ) Ok, modules loaded: Main. *Main> evenSum [1..5] 6 


以䞋は、関数がどのように機胜するかの䟋です私はごたかしおいたす。知っおいたす。埌でルヌズコンピュヌティングの問題に戻りたす。

 *Main> evenSum [1..5] accumSum 0 [1,2,3,4,5] 1 is odd accumSum 0 [2,3,4,5] 2 is even accumSum (0+2) [3,4,5] 3 is odd accumSum (0+2) [4,5] 4 is even accumSum (0+2+4) [5] 5 is odd accumSum (0+2+4) [] l == [] 0+2+4 0+6 6 

呜什型蚀語の芳点からは、すべおが正垞に芋えたす。 しかし、改善の䜙地はたくさんありたす。 手始めに、型をより䞀般的にするこずができたす。

 evenSum :: Integral a => [a] -> a 


02_Hard_Part / 11_Functions.lhs



02_Hard_Part / 12_Functions.lhs

次のステップは、 whereたたはlet定矩されたネストされた関数を䜿甚するletです。
したがっお、 accumSum関数accumSumグロヌバル名前空間を汚染したせん。

 --  2 evenSum :: Integral a => [a] -> a evenSum l = accumSum 0 l where accumSum nl = if l == [] then n else let x = head l xs = tail l in if even x then accumSum (n+x) xs else accumSum n xs 


02_Hard_Part / 12_Functions.lhs



02_Hard_Part / 13_Functions.lhs

次に、パタヌンマッチングを䜿甚したす。
 --  3 evenSum l = accumSum 0 l where accumSum n [] = n accumSum n (x:xs) = if even x then accumSum (n+x) xs else accumSum n xs 


パタヌンマッチングずは 名前付きパラメヌタヌの代わりに倀を䜿甚するだけです。 最も倧胆な堎合は、パタヌンマッチングの詳现な説明をここで調べるこずができたす 

このコヌドの代わりに foo l = if l == [] then <x> else <y>
あなたは簡単に曞くこずができたす

 foo [] = <x> foo l = <y> 


ただし、サンプルずの比范ではさらに倚くのこずができたす。 耇雑な構造の内郚デヌタを分析できたす。 亀換できたす

 foo l = let x = head l xs = tail l in if even x then foo (n+x) xs else foo n xs 

に
 foo (x:xs) = if even x then foo (n+x) xs else foo n xs 


非垞に䟿利な機胜。
これにより、コヌドがより簡朔で読みやすくなりたす。

02_Hard_Part / 13_Functions.lhs



02_Hard_Part / 14_Functions.lhs

η-reductionを䜿甚するず、Haskellで関数の蚘述を簡単にできたす。
たずえば、そのような゚ントリの代わりに

 fx = (- ) x 


䜿甚できたす

 f = -  


このアプロヌチを䜿甚しおlを取り陀くこずができたす。

 --  4 evenSum :: Integral a => [a] -> a evenSum = accumSum 0 where accumSum n [] = n accumSum n (x:xs) = if even x then accumSum (n+x) xs else accumSum n xs 

02_Hard_Part / 14_Functions.lhs



02_Hard_Part / 15_Functions.lhs


高階関数




関数をさらに矎しくするために、FEP高階関数を䜿甚できたす。
どのような動物を尋ねたすか
高階関数は、関数をパラメヌタヌずしおずる関数です。

いく぀かの䟋
 filter :: (a -> Bool) -> [a] -> [a] map :: (a -> b) -> [a] -> [b] foldl :: (a -> b -> a) -> a -> [b] -> a 


私たちは少しず぀前進したす。
 --  5 evenSum l = mysum 0 (filter even l) where mysum n [] = n mysum n (x:xs) = mysum (n+x) xs 

どこで

 filter even [1..10] ⇔ [2,4,6,8,10] 

filter関数は、匕数ずしお型 a -> Bool の関数ず型[a]リストを取りたす。 関数がtrueを返した芁玠のみを含むリストを返しtrue 。

次のステップは、サむクルをさらに簡玠化するこずです。 倀を环積できるfoldl関数を䜿甚したす。 foldl関数は、䞀般的なプログラミング手法を実装したす。
 myfunc list = foo initialValue list foo accumulated [] = accumulated foo tmpValue (x:xs) = foo (bar tmpValue x) xs 

䞊蚘のコヌドは次のものに眮き換えるこずができたす。

 myfunc list = foldl bar initialValue list 


あなたが本圓に魔法が起こっおいるかを理解したい堎合は、
foldl実装foldl以䞋に瀺したす。

 foldl fz [] = z foldl fz (x:xs) = foldl f (fzx) xs 


 foldl fz [x1,...xn] ⇔ f (... (f (fz x1) x2) ...) xn 


しかし、Haskellは遅延蚀語であるため、倀(fzx)蚈算せず(fzx)スタックにプッシュしたす。
したがっお、 foldl'代わりにfoldl foldl'を䜿甚したす。
foldl'は、 厳密なたたは粟力的な foldl実装です。

遅延関数ず厳密関数の違いが明らかでない堎合は、䞡方の関数が同じであるこずを考慮しお、コヌドを読んでください。


これで、 evenSumバヌゞョンは次のようになりたす。
 --  6 -- foldl'     --       Data.List import Data.List evenSum l = foldl' mysum 0 (filter even l) where mysum acc value = acc + value 

このコヌドは、ラムダ匏を䜿甚するこずでさらに簡単になり、䞀時的なmysum識別子をmysumたす。

 --  7 --     --     import Data.List (foldl') evenSum l = foldl' (\xy -> x+y) 0 (filter even l) 

そしおもちろん、次の亀換を行うこずができたす
 (\xy -> x+y) ⇔ (+) 

02_Hard_Part / 15_Functions.lhs



02_Hard_Part / 16_Functions.lhs

最埌に
 --  8 import Data.List (foldl') evenSum :: Integral a => [a] -> a evenSum l = foldl' (+) 0 (filter even l) 

foldl'最も盎感的な機胜ではありたせん。しかし、それは間違いなく察凊する䟡倀がありたす。

これを行うために、段階的な分析を実行したす。

 evenSum [1,2,3,4] ⇒ foldl' (+) 0 (filter even [1,2,3,4]) ⇒ foldl' (+) 0 [2,4] ⇒ foldl' (+) (0+2) [4] ⇒ foldl' (+) 2 [4] ⇒ foldl' (+) (2+4) [] ⇒ foldl' (+) 6 [] ⇒ 6 


別の有甚な高階関数はこれ(.)です。
この関数(.)は、数孊的な構成に察応しおいたす。
 (f . g . h) x ⇔ f ( g (hx)) 


これを䜿甚しお、関数をさらにη削枛できたす。
 --  9 import Data.List (foldl') evenSum :: Integral a => [a] -> a evenSum = (foldl' (+) 0) . (filter even) 


コヌドをきれいにするために䜕か他のこずができたす
 --  10 import Data.List (foldl') sum' :: (Num a) => [a] -> a sum' = foldl' (+) 0 evenSum :: Integral a => [a] -> a evenSum = sum' . (filter even) 


結果に぀いお議論したす。
高階関数を適甚するこずで䜕が埗られたしたか

コヌドの簡朔さが目を匕きたす。しかし、䞻なプラスは、プログラムを理解するこずがどれほど簡単になったかです。関数を少し倉曎したいずしたしょう。ここで、リスト内のすべおの偶数の平方和を返すように圌女に求めおいたす。

 [1,2,3,4] ▷ [1,4,9,16] ▷ [4,16] ▷ 20 


この倉曎をバヌゞョン10に远加するのは非垞に簡単です。
 squareEvenSum = sum' . (filter even) . (map (^2)) squareEvenSum' = evenSum . (map (^2)) squareEvenSum'' = sum' . (map (^2)) . (filter even) 


別の「倉換関数」を远加するだけですsquareEvenSum''他の2぀の実装よりも効率的であるこずに泚意しおください。順序は(.)本圓に重芁です。

 map (^2) [1,2,3,4] ⇔ [1,4,9,16] 


この関数mapは、パラメヌタヌ関数をリストのすべおの芁玠に適甚するだけです。関数定矩内で

䜕かを倉曎する必芁はありたせん。はるかにモゞュヌル化されおいたす。しかし、ずりわけ、圌女はより「数孊的に」振る舞い始めたした。他の関数ず同じように関数を䜿甚できたす。新しい機胜を䜿甚しお、構成、マップ、折り畳み、およびフィルタヌを実行できたす。1぀のバヌゞョンのリファクタリングをタスクずしお読者に任せたす。たずえば、この関数はリストだけでなく再垰型にも䜿甚できたす。方法に興味がある堎合-この楜しい蚘事を読んでください







Meijer、Fokkinga、Patersonによるバナナ、レンズ、゚ンベロヌプ、有刺鉄線による関数型プログラミング。

この䟋では、関数型プログラミングがどれほど匷力であるかがわかりたす。残念ながら、堎合によっおは、玔粋に機胜的な蚀語が垞に最良の遞択ずは限りたせん。少なくずも、真に普遍的な関数型蚀語はただ䜜成されおいたせん。

Haskellの際立った特城の1぀は、DSLドメむン指向蚀語の䜜成にありたす。


実際、呜什型のプログラムを䜜成したい堎合でも、Haskellは優れたツヌルです。Haskellを研究したずき、この考えは私にずっお啓瀺でした。


しかし、Haskellの超倧囜に぀いお話をする前に、もう1぀の重芁な偎面- タむプ 。

皮類



tl; dr

  • type Name = AnotherTypeそれは単に、コンパむラの皮類の別名であるNameずAnotherType同じように芋えたす。
  • data Name = NameConstructor AnotherType この䟋では、タむプが異なりたす。
  • data 再垰構造を䜜成できたす。
  • deriving それは匷力な魔術であり、あなたのための機胜を䜜成できたす。



Haskellは、匷力な静的型付けを備えた蚀語です。

なぜこれがそんなに重芁なのですか゚ラヌの数を倧幅に枛らすためです。
Haskellでは、ほずんどの゚ラヌはコンパむル段階で怜出できたす。これは、コンパむル䞭に型のコンパむルが積極的に䜿甚されるために発生したす。
間違った堎所で間違ったパラメヌタヌを䜿甚するず、コンパむラヌは簡単にこれに気付くでしょう。

型掚論



高速プログラムを䜜成するには静的型付けが必芁ですが
、ほずんどの静的型付け蚀語は抂念を䞀般化するのに匱いです。

Haskellの優れた胜力の1぀は型掚論です。

簡単な䟋。
squareHaskell 関数
 square x = x * x 


この関数はsquare、数倀型の任意の倀を平方できたす。
あなたはに倀型を枡すこずができInt、Integer、Float、FractionalずさえComplex。䟋

 % ghci GHCi, version 7.0.4: ... Prelude> let square x = x*x Prelude> square 2 4 Prelude> square 2.1 4.41 Prelude> --   Data.Complex Prelude> :m Data.Complex Prelude Data.Complex> square (2 :+ 1) 3.0 :+ 4.0 

x :+ yこれは、耇玠数x + iyの衚蚘です。

次に、Cプログラムず比范したコヌドの量を比范したす。

 int int_square(int x) { return x*x; } float float_square(float x) {return x*x; } complex complex_square (complex z) { complex tmp; tmp.real = z.real * z.real - z.img * z.img; tmp.img = 2 * z.img * z.real; } complex x,y; y = complex_square(x); 

タむプごずに、独自の関数を䜜成する必芁がありたす。これを回避するには、メタプログラミング手法を䜿甚する必芁がありたす。たずえば、プリプロセッサを䜿甚したす。C ++は、テンプレヌトを䜿甚したより矎しい゜リュヌションを提䟛したす。
 #include <iostream> #include <complex> using namespace std; template<typename T> T square(T x) { return x*x; } int main() { // int int sqr_of_five = square(5); cout << sqr_of_five << endl; // double cout << (double)square(5.3) << endl; // complex cout << square( complex<double>(5,3) ) << endl; return 0; } 


C ++バリアントはCよりも栌段に優れおいたす。
しかし、耇雑な倉数の関数の堎合、同じ構文を維持するこずは困難です。この蚘事の䟋を
ご芧ください。
。

C ++では、さたざたな型で機胜する関数を蚘述する必芁がありたす。
Haskellでは、状況は逆です。
関数はデフォルトで可胜な限り䞀般化されたす。

Haskell型掚論は、動的に型付けされた蚀語が衚す自由感を䞎えたす。
しかし、動的に型付けされた蚀語ずは異なり、ほずんどの゚ラヌはプログラムが実行される前でもキャッチされたす。
人々はこのようにHaskellに぀いお話したす

「コンパむルされた堎合、ほずんどの堎合正しく動䜜したす」




02_Hard_Part / 21_Types.lhs


新しいタむプの䜜成



独自のタむプを䜜成できたす。たずえば、型シノニムを宣蚀できたす。

 type Name = String type Color = String showInfos :: Name -> Color -> String showInfos name color = "Name: " ++ name ++ ", Color: " ++ color name :: Name name = "Robin" color :: Color color = "Blue" main = putStrLn $ showInfos name color 

しかし、これは間違いからあなたを保護するものではありたせん。
関数の2぀のパラメヌタヌを亀換しおshowInfos、プログラムを実行しおみおください。
 putStrLn $ showInfos color name 

コンパむルしお実行したす。Name、Color、およびStringを亀換可胜な゚ンティティずしお䜿甚できたす。コンパむラヌにずっおは、たったく同じです。
独自のタむプを䜜成する別の方法は、キヌワヌドを䜿甚するこずdataです。

 data Name = NameConstr String data Color = ColorConstr String showInfos :: Name -> Color -> String showInfos (NameConstr name) (ColorConstr color) = "Name: " ++ name ++ ", Color: " ++ color name = NameConstr "Robin" color = ColorConstr "Blue" main = putStrLn $ showInfos name color 

showInfosパラメヌタヌを亀換するず、コンパむラヌは誓い始めたす。間違いを犯す機䌚はなくなりたした。ただし、コヌドの量を増やすこずは犠牲になりたす。

型コンストラクタは関数であるこずに泚意しおください。

 NameConstr :: String -> Name ColorConstr :: String -> Color 


data通垞、䜿甚方法は次のようになりたす。

 data TypeName = ConstructorName [types] | ConstructorName2 [types] | ... 

のために
DataTypeNameずDataTypeConstructorは通垞同じ名前を䜿甚したす。
䟋
 data Complex = Num a => Complex aa 

゚ントリの構文を䜿甚するこずもできたす。
 data DataTypeName = DataConstructor { field1 :: [type of field1] , field2 :: [type of field2] ... , fieldn :: [type of fieldn] } 


たた、倚数のデヌタアクセス関数が自動的に䜜成されたす。さらに、新しい倀を曞き蟌むずき、任意の順序のフィヌルドを䜿甚できたす。

䟋

 data Complex = Num a => Complex { real :: a, img :: a} c = Complex 1.0 2.0 z = Complex { real = 3, img = 4 } real c ⇒ 1.0 img z ⇒ 4 

02_Hard_Part / 22_Types.lhs



02_Hard_Part / 23_Types.lhs

再垰型



リストを䜿甚しお、再垰型の代衚者ず既に䌚っおいたす。より詳现な構文を䜿甚しお、リストを最初から䜜成できたす。

 data List a = Empty | Cons a (List a) 


蚘録を簡単にするために、䞭眮型コンストラクタヌを䜿甚できたす。

 infixr 5 ::: data List a = Nil | a ::: (List a) 

埌の数字infixrは、オペレヌタヌの優先順䜍です。新しいタむプの倀

を印刷Show、読み取りRead、チェックEq、比范Ordする堎合、必芁な関数を自動的に印刷するようにHaskellに指瀺できたす。

 infixr 5 ::: data List a = Nil | a ::: (List a) deriving (Show,Read,Eq,Ord) 


deriving (Show)タむプの説明に远加するず、Haskellは自動的に関数を䜜成したすshow。すぐにあなたの関数のバヌゞョンを曞く方法を芋るでしょうshow。

 convertList [] = Nil convertList (x:xs) = x ::: convertList xs 


 main = do print (0 ::: 1 ::: Nil) print (convertList [0,1]) 


プログラム出力
 0 ::: (1 ::: Nil) 0 ::: (1 ::: Nil) 


02_Hard_Part / 23_Types.lhs



02_Hard_Part / 30_Trees.lhs


朚々




もう1぀の暙準的な䟋を次に瀺したす。バむナリツリヌ。

 import Data.List data BinTree a = Empty | Node a (BinTree a) (BinTree a) deriving (Show) 

リストを順序付き二分朚に倉換する関数を曞きたしょう。
 treeFromList :: (Ord a) => [a] -> BinTree a treeFromList [] = Empty treeFromList (x:xs) = Node x (treeFromList (filter (<x) xs)) (treeFromList (filter (>x) xs)) 


この機胜の優雅さをお楜しみください。
ロシア語ぞの翻蚳



 main = print $ treeFromList [7,2,4,8] 


次のようなものが埗られるはずです。

 Node 7 (Node 2 Empty (Node 4 Empty Empty)) (Node 8 Empty Empty) 


これは有益ですが、私たちのツリヌをあたり矎しく衚珟しおいたせん。

02_Hard_Part / 30_Trees.lhs



02_Hard_Part / 31_Trees.lhs

楜しい時間を過ごすために、ツリヌをより矎しく衚瀺するためのコヌドを曞きたしょう。どんな朚でもうたく衚瀺できる関数を曞くのは本圓に楜しかったです。この郚分が難しいず思われる堎合は、安党にスキップできたす。

小さな倉曎を加える必芁がありたす。型宣蚀から
削陀deriving (Show)しBinTreeたす。 BinTreeを型クラスEqおよびOrdのむンスタンスにするこずも圹立ちたす。これにより、ツリヌの等䟡性をテストし、比范する機䌚が埗られたす。

 data BinTree a = Empty | Node a (BinTree a) (BinTree a) deriving (Eq,Ord) 

なしではderiving (Show)、Haskellは私たちのためのメ゜ッドを䜜成したせんshow。
ただし、独自のバヌゞョンを䜜成したすshow。これを行うにBinTree a
は、新しく䜜成された型が型classのむンスタンスであるこずを瀺す必芁がありたすShow。
コヌドでは、次のようになりたす。

 instance Show (BinTree a) where show t = ... --      


ここに、バむナリツリヌを衚瀺する私のバヌゞョンがありたす。芋た目ほど怖くないので、もっず奇劙なオブゞェクトを衚瀺するように調敎したした。

 --   BinTree   Show instance (Show a) => Show (BinTree a) where --     '<' --   :    show t = "< " ++ replace '\n' "\n: " (treeshow "" t) where -- treeshow pref Tree --        pref --       treeshow pref Empty = "" -- Leaf treeshow pref (Node x Empty Empty) = (pshow pref x) --    treeshow pref (Node x left Empty) = (pshow pref x) ++ "\n" ++ (showSon pref "`--" " " left) --    treeshow pref (Node x Empty right) = (pshow pref x) ++ "\n" ++ (showSon pref "`--" " " right) --        treeshow pref (Node x left right) = (pshow pref x) ++ "\n" ++ (showSon pref "|--" "| " left) ++ "\n" ++ (showSon pref "`--" " " right) --      showSon pref before next t = pref ++ before ++ treeshow (pref ++ next) t -- pshow  "\n"  "\n"++pref pshow pref x = replace '\n' ("\n"++pref) (show x) --    replace c new string = concatMap (change c new) string where change c new x | x == c = new | otherwise = x:[] -- "x" 


メ゜ッドtreeFromListは倉曎されたせん。
 treeFromList :: (Ord a) => [a] -> BinTree a treeFromList [] = Empty treeFromList (x:xs) = Node x (treeFromList (filter (<x) xs)) (treeFromList (filter (>x) xs)) 


そしお今、私たちは遊ぶこずができたす

 main = do putStrLn "Int binary tree:" print $ treeFromList [7,2,4,8,1,3,6,21,12,23] 


 print $ treeFromList [7,2,4,8,1,3,6,21,12,23] Int binary tree: < 7 : |--2 : | |--1 : | `--4 : | |--3 : | `--6 : `--8 : `--21 : |--12 : `--23 


はるかに良いツリヌのルヌトは、シンボルずずもに衚瀺されたす<。そしお、埌続の各行はで始たりたす:。
ただし、別のタむプを䜿甚できたす。

 putStrLn "\nString binary tree:" print $ treeFromList ["foo","bar","baz","gor","yog"] 


 String binary tree: < "foo" : |--"bar" : | `--"baz" : `--"gor" : `--"yog" 


たた、ツリヌの等䟡性をテストしお順序を蚭定できるため、ツリヌツリヌを䜜成できたす。

 putStrLn "\n      :" print ( treeFromList (map treeFromList ["baz","zara","bar"])) 


       : < < 'b' : : |--'a' : : `--'z' : |--< 'b' : | : |--'a' : | : `--'r' : `--< 'z' : : `--'a' : : `--'r' 

これが、各新しい行のプレフィックスずしおルヌトを陀くを遞択した理由:です。



 putStrLn "\nTree of Binary trees of Char binary trees:" print $ (treeFromList . map (treeFromList . map treeFromList)) [ ["YO","DAWG"] , ["I","HEARD"] , ["I","HEARD"] , ["YOU","LIKE","TREES"] ] 


これは以䞋ず同等です
 print ( treeFromList ( map treeFromList [ map treeFromList ["YO","DAWG"] , map treeFromList ["I","HEARD"] , map treeFromList ["I","HEARD"] , map treeFromList ["YOU","LIKE","TREES"] ])) 

結果ずしお

 Binary tree of Binary trees of Char binary trees: < < < 'Y' : : : `--'O' : : `--< 'D' : : : |--'A' : : : `--'W' : : : `--'G' : |--< < 'I' : | : `--< 'H' : | : : |--'E' : | : : | `--'A' : | : : | `--'D' : | : : `--'R' : `--< < 'Y' : : : `--'O' : : : `--'U' : : `--< 'L' : : : `--'I' : : : |--'E' : : : `--'K' : : `--< 'T' : : : `--'R' : : : |--'E' : : : `--'S' 

重耇は挿入されないこずに泚意しおください。
察応する1぀のツリヌのみが衚瀺されたす"I","HEARD"。Treeをむンスタンスず宣蚀したため、この機䌚はほが無料で埗られたしたEq。

このタむプの矎しさず力を芋おください。数字、線、蚘号だけでなく、他のツリヌで構成されるツリヌを䜜成できたす。芁玠がツリヌツリヌであるツリヌを䜜成するこずもできたす


02_Hard_Part / 31_Trees.lhs



02_Hard_Part / 40_Infinites_Structures.lhs

無限の構造



Haskellはしばしば怠zyな蚀語ず呌ばれたす。

しかし、Haskellは厳密な蚀語ではないず蚀うのは正しいこずです。怠azineは、厳密ではない蚀語の䞀般的な実装です。

緩い蚀語ずは䜕ですかHaskell-wikiから

リダクション匏を蚈算するための数孊甚語は、倖偎から内偎に進みたす。

匏がある堎合は、(a+(b*c))最初+に評䟡し、次に内郚匏(b*c)


たずえば、Haskellを䜿甚するず、次のこずができたす。

 -- numbers = [1,2,..] numbers :: [Integer] numbers = 0:map (1+) numbers take' n [] = [] take' 0 l = [] take' n (x:xs) = x:take' (n-1) xs main = print $ take' 10 numbers 


そしお、プログラムは終了したす。

どうやっお

すべおを蚈算する代わりに、必芁な芁玠のみを蚈算したす。

Haskellで無限リストを指定する構文に泚意しおください

 [1..] ⇔ [1,2,3,4...] [1,3..] ⇔ [1,3,5,7,9,11...] 


そしお、ほずんどの機胜はそのようなリストを扱うこずができたす。take私たちのものず同䞀の暙準機胜さえありtake'たす。

02_Hard_Part / 40_Infinites_Structures.lhs



02_Hard_Part / 41_Infinites_Structures.lhs

順序付けられた二分朚を持぀こずを気にしないずしたしょう。無限バむナリツリヌの䟋を次に瀺したす。

 nullTree = Node 0 nullTree nullTree 


各ノヌドにれロがある完党な二分朚。次の関数を䜿甚しおこのツリヌを操䜜できるこずを蚌明したす。

 --    BinTree --      treeTakeDepth _ Empty = Empty treeTakeDepth 0 _ = Empty treeTakeDepth n (Node x left right) = let nl = treeTakeDepth (n-1) left nr = treeTakeDepth (n-1) right in Node x nl nr 


このプログラムが出力するものを芋おみたしょう

 main = print $ treeTakeDepth 4 nullTree 


このコヌドは、次の結果をコンパむル、開始、停止、衚瀺したす。
 < 0 : |-- 0 : | |-- 0 : | | |-- 0 : | | `-- 0 : | `-- 0 : | |-- 0 : | `-- 0 : `-- 0 : |-- 0 : | |-- 0 : | `-- 0 : `-- 0 : |-- 0 : `-- 0 

ニュヌロンを少し揺らすには、ツリヌをより奇劙にしたす。

 iTree = Node 0 (dec iTree) (inc iTree) where dec (Node xlr) = Node (x-1) (dec l) (dec r) inc (Node xlr) = Node (x+1) (inc l) (inc r) 

同様のツリヌは、高次関数を䜿甚しお䜜成するこずもできたす。
この関数はに䌌おmapいBinTreeたすが、リストの代わりに機胜するはずです。
このような関数の可胜な実装の1぀を次に瀺したす。
 --      Tree treeMap :: (a -> b) -> BinTree a -> BinTree b treeMap f Empty = Empty treeMap f (Node x left right) = Node (fx) (treeMap f left) (treeMap f right) 


ヒントこのトピックにこだわるこずはもうありたせん。あなたが䞀般化を芋お奜奇心旺盛であればmap他のデヌタ構造に、キヌワヌドファンクタファンクタを怜玢しfmap。

私たちの実装

 infTreeTwo :: BinTree Int infTreeTwo = Node 0 (treeMap (\x -> x-1) infTreeTwo) (treeMap (\x -> x+1) infTreeTwo) 

そしお、実行の結果
 main = print $ treeTakeDepth 4 infTreeTwo 


 < 0 : |-- -1 : | |-- -2 : | | |-- -3 : | | `-- -1 : | `-- 0 : | |-- -1 : | `-- 1 : `-- 1 : |-- 0 : | |-- -1 : | `-- 1 : `-- 2 : |-- 1 : `-- 3 


02_Hard_Part / 41_Infinites_Structures.lhs

Source: https://habr.com/ru/post/J152889/


All Articles