Looking for haskell Answers? Try Ask4KnowledgeBase
Looking for haskell Keywords? Try Ask4Keywords

Haskell Languageハスケル言語を使い始める


備考

ハスケルのロゴ

Haskellは高度な純関数プログラミング言語です。

特徴:

  • 静的型: Haskellのすべての式は、コンパイル時に決定される型を持ちます。静的型チェックは、プログラムのテキスト(ソースコード)の解析に基づいて、プログラムの型安全性を検証するプロセスです。プログラムがスタティック・タイプ・チェッカを通過すると、プログラムはすべての可能な入力に対してタイプ安全性プロパティの一部を満たすことが保証されます。
  • 純粋に機能的 :ハスケルのすべての関数は、数学的な意味での関数です。ステートメントや命令はなく、変数(ローカルまたはグローバル)を変更できない式や時間や乱数のような状態にアクセスできない式だけです。
  • Concurrent:その主力コンパイラであるGHCには、高性能パラレルガベージコレクタと、多数の有用な並行処理プリミティブと抽象概念を含む軽量同時実行ライブラリが付属しています。
  • レイジー評価:関数は引数を評価しません。値が必要になるまで式の評価を遅らせます。
  • 汎用: Haskellはすべてのコンテキストおよび環境で使用できるように構築されています。
  • パッケージ: Haskellへのオープンソースの貢献は非常に活発であり、パブリックパッケージサーバー上では幅広いパッケージが利用できます。

Haskellの最新の標準はHaskell 2010です.2016年5月現在、グループは次のバージョンのHaskell 2020に取り組んでいます。

Haskell公式文書は包括的かつ有用な情報源です。本、コース、チュートリアル、マニュアル、ガイドなどを見つけるのに最適な場所

バージョン

バージョン発売日
ハスケル2010 2012-07-10
ハスケル98 2002-12-01

入門

オンラインREPL

Haskellの作成を始める最も簡単な方法は、おそらくHaskellのウェブサイトまたはHaskell試して、 ホームページでオンラインのREPL(read-eval-print-loop)を使うことです。オンラインのREPLは、ほとんどの基本機能とIOをサポートしています。また、基本的なチュートリアルも用意されています。このチュートリアルは、コマンドhelp 入力することで開始できます。ハスケルの基礎を学び、いくつかのことを試してみるのに理想的なツールです。

GHC(i)

もう少し関与する準備ができているプログラマにとっては、 Glorious / Glasgow Haskellコンパイラに付属する対話型環境であるGHCiがあります。 GHCは別々にインストールできますが、これはコンパイラだけです。新しいライブラリをインストールするには、 CabalStackなどのツールもインストールする必要があります。 Unixライクなオペレーティングシステムを実行している場合、最も簡単にインストールするには、次のコマンドを使用してStackをインストールします。

curl -sSL https://get.haskellstack.org/ | sh
 

これにより、システムの残りの部分から隔離されたGHCがインストールされるため、簡単に削除できます。すべてのコマンドはstack で先行する必要があります。別の簡単なアプローチは、 Haskell Platformをインストールすることです。プラットフォームは2つの味があります:

  1. 最小限のディストリビューションにはGHC (コンパイル用)とCabal / Stack (パッケージのインストールとビルド用)
  2. フルディストリビューションには、プロジェクトの開発、プロファイリング、カバレッジ分析のためのツールが追加されています。また、広く使用されている追加のパッケージも含まれています。

これらのプラットフォームは、インストーラをダウンロードし 、指示に従って、またはディストリビューションのパッケージマネージャを使用してインストールできます(このバージョンは最新であるとは限りません)。

  • Ubuntu、Debian、Mint:

    sudo apt-get install haskell-platform
     
  • Fedora:

    sudo dnf install haskell-platform
     
  • レッドハット:

    sudo yum install haskell-platform
     
  • アーチLinux:

    sudo pacman -S ghc cabal-install haskell-haddock-api \
                   haskell-haddock-library happy alex
     
  • Gentoo:

    sudo layman -a haskell
    sudo emerge haskell-platform
     
  • 自作のOSX:

    brew cask install haskell-platform
     
  • MacPortsを使用したOSX:

    sudo port install haskell-platform
     

インストールが完了したら、端末のどこにでもghci コマンドを起動してGHCiを起動することができます。インストールがうまくいったら、コンソールは次のように見えるはずです

me@notebook:~$ ghci
GHCi, version 6.12.1: http://www.haskell.org/ghc/  :? for help
Prelude> 
 

おそらくPrelude> 前にどのライブラリがロードされているかについての情報があります。さて、コンソールはHaskell REPLになり、オンラインREPLのようにHaskellコードを実行することができます。この対話型環境を終了するには、 :q または:quit ます。 GHCiで利用できるコマンドの詳細については、次のように入力し :? 開始画面に示されているように。

同じ行を何度も同じ行に書くことは、必ずしも実際上ではないので、Haskellのコードをファイルに書き込むことは良い考えです。これらのファイルは、通常、拡張子として.hs を持ち、 :l または:load を使用してREPLにロードすることができ:load

前述のように、 GHCiGHCの一部であり、実際にはコンパイラです。このコンパイラを使用して、Haskellコードを持つ.hs ファイルを実行中のプログラムに変換することができます。 .hs ファイルには多くの関数が含まれている可能性があるため、 main 関数をファイルに定義する必要があります。これがプログラムの出発点になります。 test.hs ファイルは、以下のコマンドでコンパイルできます。

ghc test.hs
 

エラーがなく、 main 関数が正しく定義されていれば、オブジェクトファイルと実行可能ファイルが作成されます。

高度なツール

  1. 以前はパッケージマネージャーとしてすでに言及されていましたが、 スタックはHaskell開発のための便利なツールになりました。インストールされると、

    • GHCのインストール(複数バージョン)
    • プロジェクトの作成と足場
    • 依存関係管理
    • プロジェクトの構築とテスト
    • ベンチマーク
  2. IHaskellはIPython用のhaskellカーネルでマークダウンと数学的表記法を組み合わせて(実行可能な)コードを作成することができます。

値の宣言

REPLでは次のように一連の式を宣言できます。

Prelude> let x = 5
Prelude> let y = 2 * 5 + x
Prelude> let result = y * 10
Prelude> x
5
Prelude> y
15
Prelude> result
150
 

ファイルに同じ値を宣言するために、次のように記述します。

-- demo.hs

module Demo where
-- We declare the name of our module so 
-- it can be imported by name in a project.

x = 5

y = 2 * 5 + x

result = y * 10
 

モジュール名は、変数名とは異なり、大文字になります。

要因

階乗関数はHaskell "Hello World!"です。 (そして一般的には関数型プログラミングのために)、言語の基本原則を簡潔に示しているという意味で使われています。

バリエーション1

fac :: (Integral a) => a -> a
fac n = product [1..n]
 

ライブデモ

  • Integral は整数型のクラスです。例には、 IntInteger ます。
  • (Integral a) => 前記クラスに入る型a に制約を置く
  • fac :: a -> a は、 fac はaをとり、 a を返す関数であるa
  • product は、リスト内のすべての数値を掛け合わせることによって累積する関数です。
  • [1..n] は、 enumFromTo 1 n と呼ばれる特別な記法であり、 1 ≤ x ≤ n 範囲である。

バリエーション2

fac :: (Integral a) => a -> a
fac 0 = 1
fac n = n * fac (n - 1)
 

ライブデモ

このバリエーションでは、パターンマッチングを使用して、関数定義を別々のケースに分割します。引数が0 (ストップ条件と呼ばれることもあります)と2番目の定義(定義の順序が重要です)の場合、最初の定義が呼び出されます。また、 fac はそれ自体を参照するため、再帰を例示しています。


書き換えルールのために、最適化を有効にしてGHCを使用すると、両方のバージョンのfac が同じマシンコードにコンパイルされることに注意してください。したがって、効率の面では、2つは同等です。

フィボナッチ、レイジー評価の使用

レイジー評価は、Haskellが値が必要なリスト項目だけを評価することを意味します。

基本的な再帰的定義は次のとおりです。

f (0)  <-  0
f (1)  <-  1
f (n)  <-  f (n-1) + f (n-2)
 

直接評価すると、 非常に遅くなります。しかし、すべての結果を記録するリストがあると想像してください。

fibs !! n  <-  f (n) 
 

その後、

                  ┌──────┐   ┌──────┐   ┌──────┐
                  │ f(0) │   │ f(1) │   │ f(2) │
fibs  ->  0 : 1 : │  +   │ : │  +   │ : │  +   │ :  .....
                  │ f(1) │   │ f(2) │   │ f(3) │
                  └──────┘   └──────┘   └──────┘

                  ┌────────────────────────────────────────┐
                  │ f(0)   :   f(1)   :   f(2)   :  .....  │ 
                  └────────────────────────────────────────┘
      ->  0 : 1 :               +
                  ┌────────────────────────────────────────┐
                  │ f(1)   :   f(2)   :   f(3)   :  .....  │
                  └────────────────────────────────────────┘
 

これは次のようにコード化されます:

fibn n = fibs !! n
    where
    fibs = 0 : 1 : map f [2..]
    f n = fibs !! (n-1) + fibs !! (n-2)
 

または

GHCi> let fibs = 0 : 1 : zipWith (+) fibs (tail fibs)
GHCi> take 10 fibs
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
 

zipWith zipWith (+) [x1, x2, ...] [y1, y2, ...][x1 + y1, x2 + y2, ...] zipWith (+) [x1, x2, ...] [y1, y2, ...] に等しいので、与えられた2項関数を与えられた2つのリストの対応する要素に適用することによってリストをzipWith する。 [x1 + y1, x2 + y2, ...]

fibs を書く別の方法は、 scanl 関数です:

GHCi> let fibs = 0 : scanl (+) 1 fibs
GHCi> take 10 fibs
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
 

scanl 部分的な結果のリスト作成しますfoldl 入力リストに沿って左から右に取り組んで、生じるであろうが。すなわち、 scanl f z0 [x1, x2, ...]scanl f z0 [x1, x2, ...] [z0, z1, z2, ...] where z1 = f z0 x1; z2 = f z1 x2; ... と等しく[z0, z1, z2, ...] where z1 = f z0 x1; z2 = f z1 x2; ...

怠惰な評価のおかげで、両方の関数は完全に計算することなく無限リストを定義します。つまり、無限のフィボナッチ数列のn番目の要素を検索してfib 関数を書くことができます:

GHCi> let fib n = fibs !! n  -- (!!) being the list subscript operator
-- or in point-free style:
GHCi> let fib = (fibs !!)
GHCi> fib 9
34
 

こんにちは世界!

基本的な"こんにちは、世界!" Haskellのプログラムは、1行または2行で簡潔に表現することができます。

main :: IO ()
main = putStrLn "Hello, World!"
 

最初の行は、 main がtype () 値を "計算"するI / Oアクションを表すIO () 型の値であることを示すオプションの型注釈です( "unit"を読む;情報を伝えない空のタプル)外の世界にいくつかの副作用を実行するだけでなく(ここでは、端末で文字列を印刷します)。この型の注釈は、 main 唯一可能な型であるため、通常は省略されます。

これをhelloworld.hs ファイルに入れ、GHCなどのHaskellコンパイラを使用してコンパイルします。

ghc helloworld.hs
 

コンパイルされたファイルを実行すると、結果は"Hello, World!" 画面に印刷されます。

./helloworld
Hello, World!
 

代わりに、 runhaskell またはrunghc すると、プログラムをコンパイルせずにインタプリタモードで実行することができます。

runhaskell helloworld.hs
 

インタラクティブなREPLは、コンパイルする代わりに使用することもできます。これは、GHCコンパイラに付属のghci など、ほとんどのHaskell環境に付属しています。

ghci> putStrLn "Hello World!"
Hello, World!
ghci> 
 

代替的に、使用してファイルからGHCiのにスクリプトをロードしload (または:l )。

ghci> :load helloworld
 

:reload (または:r )はghciのすべてをリロードします:

Prelude> :l helloworld.hs 
[1 of 1] Compiling Main             ( helloworld.hs, interpreted )

<some time later after some edits>

*Main> :r
Ok, modules loaded: Main.
 

説明:

この最初の行は、型の宣言であり、 main の型を宣言しています。

main :: IO ()
 

タイプIO () 値は、外部世界と対話できるアクションを記述する。

Haskellは自動型推論を可能にする本格的なHindley-Milner型システムを持っているので、型シグネチャは技術的にオプションです。単にmain :: IO () 省略すると、コンパイラは型を独自に推論することができます。 main定義を分析する。しかし、トップレベル定義のための型シグネチャを記述しないことは、非常に不適切なスタイルであると考えられます。その理由は次のとおりです。

  • Haskellの型シグネチャは、型システムが非常に表現的であるため、その型を見れば関数がどのようなものであるかをよく知ることができるので、非常に参考になります。この「ドキュメンテーション」は、GHCiのようなツールで簡単にアクセスできます。また、通常のドキュメンテーションとは異なり、コンパイラの型チェッカーは関数定義と実際に一致するかどうかを確認します。

  • 型シグネチャはバグをローカルに保ちます。型シグネチャを指定せずに定義を間違えた場合、コンパイラはただちにエラーを報告するのではなく、単純に無意味な型を推論するだけで、実際に型チェックを行います。その値を使用しているときに、謎めいたエラーメッセージが表示されることがあります。シグネチャを使用すると、コンパイラは発生した場所でバグを発見することができます。

この2行目は実際の作業を行います。

main = putStrLn "Hello, World!"
 

あなたが命令的言語から来たのであれば、この定義は次のように書くこともできます。

main = do {
   putStrLn "Hello, World!" ;
   return ()
   }
 

または同等に(Haskellにはレイアウトベースの解析がありますが、このメカニズムを混乱させる不整合なタブとスペースを混ぜることに注意してください ):

main = do
    putStrLn "Hello, World!"
    return ()
 

do ブロック内の各行は、 モナド (ここではI / O) 計算を表しているので、 do ブロック全体は、これらのサブステップからなる全体のアクションを、指定されたモナドに固有の方法で組み合わせることによって表します(I / Oこれはただちにそれらを実行することを意味します)。

do 構文はそれ自体がここのIO ようなモナドの構文的な砂糖であり、 return は、特定のモナド定義の一部であるかもしれない副作用や追加の計算を行わずに引数を生成するノーオペレーションアクションです。

上記はmain = putStrLn "Hello, World!" 定義と同じmain = putStrLn "Hello, World!" なぜなら、 putStrLn "Hello, World!" という値がputStrLn "Hello, World!" 既にIO () 型を持っています。 "ステートメント"として表示され、 putStrLn "Hello, World!" 完全なプログラムとみなすことができ、このプログラムを参照するためにmain を定義するだけです。

あなたputStrLn の署名をオンラインで調べることができます

putStrLn :: String -> IO ()
-- thus,
putStrLn (v :: String) :: IO ()
 

putStrLn は、引数として文字列をとり、I / Oアクション(つまり、ランタイムが実行できるプログラムを表す値)を出力する関数です。ランタイムは常にmain という名前のアクションを実行するので、単にputStrLn "Hello, World!" と同じにする必要がありますputStrLn "Hello, World!"

首相

いくつかの最も顕著な変形:

100未満

import Data.List ( (\\) )

ps100 = ((([2..100] \\ [4,6..100]) \\ [6,9..100]) \\ [10,15..100]) \\ [14,21..100]

   -- = (((2:[3,5..100]) \\ [9,15..100]) \\ [25,35..100]) \\ [49,63..100]

   -- = (2:[3,5..100]) \\ ([9,15..100] ++ [25,35..100] ++ [49,63..100])
 

無制限

データ整列パッケージを使用したEratosthenesのふるい:

import qualified Data.List.Ordered

ps   = 2 : _Y ((3:) . minus [5,7..] . unionAll . map (\p -> [p*p, p*p+2*p..]))

_Y g = g (_Y g)   -- = g (g (_Y g)) = g (g (g (g (...)))) = g . g . g . g . ...
 

伝統的な

(準最適試行篩)

ps = sieve [2..]
     where
     sieve (x:xs) = [x] ++ sieve [y | y <- xs, rem y x > 0]

-- = map head ( iterate (\(x:xs) -> filter ((> 0).(`rem` x)) xs) [2..] )
 

最適試行分割

ps = 2 : [n | n <- [3..], all ((> 0).rem n) $ takeWhile ((<= n).(^2)) ps]

-- = 2 : [n | n <- [3..], foldr (\p r-> p*p > n || (rem n p > 0 && r)) True ps]
 

移行

試行部門からエラトステネスのふるいまで:

[n | n <- [2..], []==[i | i <- [2..n-1], j <- [0,i..n], j==n]]
 

最短コード

nubBy (((>1).).gcd) [2..]          -- i.e., nubBy (\a b -> gcd a b > 1) [2..]
 

nubBy(\\) ようにData.List からのものです。