Haskellの大騒ぎは何ですか?[閉まっている]


109

Haskellについて話をしているプログラマーが何人かいるのを知っています。そのため、ここでは誰もがその言語を気に入っているようです。Haskellが得意であることは、天才プログラマーの特徴のようです。

誰かがなぜそれがそんなにエレガントで優れているのかを示すいくつかのHaskellの例を挙げられますか?

回答:


134

それが私に提案された方法、そして今1か月間Haskellでの学習に取り組んだ後に私が本当だと思うのは、関数型プログラミングが興味深い方法であなたの脳をねじるという事実です。 :ループの代わりに、マップ、フォールド、フィルターなどを検討します。一般に、問題について複数の視点がある場合、この問題について推論し、必要に応じて視点を切り替えることができます。

Haskellのもう1つの優れた点は、その型システムです。厳密に型付けされていますが、型推論エンジンは、型に関連する愚かな間違いをしたことを魔法のように教えてくれるPythonプログラムのように感じさせます。この点に関するHaskellのエラーメッセージはいくぶん欠けていますが、言語に慣れるにつれて、自分に言い聞かせるでしょう。これがタイピングの本来の目的です!


47
Haskellのエラーメッセージはghcが欠けているわけではないことに注意してください。Haskell標準では、エラーメッセージの処理方法を指定していません。
PyRulez 14

私のような人々のために、GHCはグラスゴーハスケルコンパイラの略です。en.wikipedia.org/wiki/Glasgow_Haskell_Compiler
Loremのイプサム

137

これは Haskellを学ぶように私を説得し例です(そして少年は私がそうしたことがうれしいです)。

-- program to copy a file --
import System.Environment

main = do
         --read command-line arguments
         [file1, file2] <- getArgs

         --copy file contents
         str <- readFile file1
         writeFile file2 str

OK、それは短くて読みやすいプログラムです。その意味では、Cプログラムよりも優れています。しかし、これは(たとえば)非常によく似た構造のPythonプログラムとどう違うのでしょうか。

答えは遅延評価です。ほとんどの言語(一部の機能的な言語でも)では、上記のような構造のプログラムでは、ファイル全体がメモリに読み込まれ、新しい名前で再び書き込まれます。

Haskellは「怠惰」です。必要になるまで計算を行わず、ひいては必要のないものを計算しません。たとえば、このwriteFile行を削除したとしても、Haskellはそもそもファイルから何も読み込まないでしょう。

現状では、HaskellはがにwriteFile依存していることを認識しているreadFileため、このデータパスを最適化できます。

結果はコンパイラに依存しますが、上記のプログラムを実行すると通常は次のようになります。プログラムは最初のファイルのブロック(たとえば8KB)を読み取り、2番目のファイルに書き込み、次に最初のファイルから別のブロックを読み取りますファイル、2番目のファイルなどに書き込みます。(実行straceしてみてください!)

...これは、ファイルコピーの効率的なC実装が行うこととよく似ています。

したがって、Haskellを使用すると、多くの場合パフォーマンスを犠牲にすることなく、コンパクトで読みやすいプログラムを作成できます。

私が追加しなければならないもう1つのことは、Haskellがバグのあるプログラムの作成を単純に難しくしていることです。驚くべき型システム、副作用の欠如、そしてもちろんHaskellコードのコンパクトさは、少なくとも3つの理由でバグを減らします。

  1. プログラム設計の改善。複雑さが軽減されると、論理エラーが減少します。

  2. コンパクトなコード。バグが存在する行が少なくなります。

  3. エラーをコンパイルします。多くのバグが有効なHaskellではありません

Haskellは万人向けではありません。しかし、誰もがそれを試してみるべきです。


8KB定数(またはそれが何であれ)をどのように正確に変更しますか?それ以外の場合、特にプリフェッチを行わないと、Haskellの実装はCバージョンよりも遅くなると私は思うので...
user541686

1
@Mehrdadでバッファサイズを変更できますhSetBuffering handle (BlockBuffering (Just bufferSize))
David

3
この回答が116の賛成票を持っているのは驚くべきことですが、そこにあるものは間違っています。Haskellが遅延(非厳密)言語であることとは関係のない遅延バイト文字列(で実行できます)を使用しない限り、このプログラムファイル全体読み取りますData.Bytestring.Lazy.readFile。モナドは順序付けられています -これは、大まかに「結果を取り出すときにすべての副作用が行われる」ことを意味します。「怠惰なバイト文字列」の魔法については、これは危険であり、他のほとんどの言語では同様またはより単純な構文で実行できます。
Jo So

14
退屈な古い標準readFileも同様に遅延IOを行いData.ByteString.Lazy.readFileます。したがって、答えは間違っていませんし、コンパイラの最適化だけではありません。実際、これはHaskellの仕様の一部です。「readFile関数はファイルを読み取り、ファイルの内容を文字列として返します。ファイルは、のようにオンデマンドで遅延して読み取られgetContentsます。」
Daniel Wagner

1
他の回答は、Haskellについてより特別なことを指摘していると思います。多くの言語/環境にはストリームがあります。Node:でも同様のことができますconst fs = require('fs'); const [file1, file2] = process.argv.slice(2); fs.createReadStream(file1).pipe(fs.createWriteStream(file2))。バッシュは、あまりにも、似た何かを持っている:cat $1 > $2
マックスHeiber

64

あなたは一種の間違った質問をしています。

Haskellはあなたがいくつかのクールな例を見て行くと行く言語ではない「なるほど、私は今見る、、それは良いものを作ります!」

それは、私たちがこれらすべての他のプログラミング言語を持っているようなものであり、それらは多かれ少なかれ類似しています。そして、あなたが奇抜さに慣れると完全に異なる方法で完全に異なる奇抜なHaskellがあります。しかし問題は、奇抜さに慣れるのにかなり時間がかかることです。Haskellを他のほぼすべての主流言語と区別するもの:

  • 遅延評価
  • 副作用なし(すべてが純粋で、IOなどはモナドを介して発生します)
  • 信じられないほど表現力豊かな静的型システム

多くの主流言語とは異なる(しかし一部で共有されている)他のいくつかの側面:

  • 機能的
  • 重要な空白
  • 推定されるタイプ

他のいくつかのポスターが答えたように、これらすべての機能の組み合わせは、まったく異なる方法でプログラミングについて考えることを意味します。そして、これをJoe-mainstream-programmerに適切に伝える例(または例のセット)を思いつくのは困難です。それは体験的なものです。(例えとして、1970年の中国旅行の写真を紹介できますが、写真を見ても、当時の中国の生活がどうなっていたかはわかりません。同様に、Haskellを紹介できます。 'クイックソート'ですが、Haskellerとはどういう意味かわかりません。)


17
私はあなたの最初の文に同意しません。私は最初にHaskellコードのいくつかの例に本当に感銘を受けました、そしてそれが学ぶ価値があると本当に確信したのはこの記事でした:でした cs.dartmouth.edu/~doug/powser.html もちろん、これは数学者/物理学者にとって興味深いものです。実世界のものを調べているプログラマーは、この例がばかげていることに気付くでしょう。
Rafael S. Calsaverini、2009年

2
@Rafael:それは「現実世界のものを調べているプログラマーが何に感銘を受けるか」という疑問を引き起こしますか?
JD

良い質問!私は「現実の世界」のプログラマーではないので、彼らが何を好むのかわかりません。ははは...私は物理学者や数学者が好きなのを知っています。:P
ラファエルS.カルサヴェリーニ

27

実際にHaskellを際立たせているのは、関数型プログラミングを実施するための設計での取り組みです。ほぼすべての言語で関数型のスタイルでプログラミングできますが、最初の都合で放棄するのは非常に簡単です。Haskellでは関数型プログラミングを放棄することはできないため、論理的な結論に到達する必要があります。論理的な結論は、推論しやすい最終的なプログラムであり、最も厄介なタイプのバグのクラス全体を回避します。

実際に使用するためのプログラムを作成する場合、Haskellは実用的な方法で不足しているかもしれませんが、最初の既知のHaskellを使用するためには、最終的なソリューションの方が優れています。私はまだそこにいませんが、これまでのところ、Haskellを学ぶことは言うよりもはるかに啓蒙的でした、Lispは大学にいました。


1
ええと、いつでも、そして常にSTモナドだけを使用する可能性があります。そして/またはunsafePerformIO世界の火傷を見たいだけの人のために;)
sara

22

大騒ぎの一部は、純粋さと静的型付けにより、積極的な最適化と組み合わせた並列処理が可能になることです。並列言語は今やホットで、マルチコアは少し破壊的です。

Haskellは、高速なネイティブコードコンパイラとともに、ほとんどの汎用言語よりも多くの並列処理オプションを提供します。並列スタイルのこの種のサポートとの競争は本当にありません:

したがって、マルチコアを動作させることに関心がある場合、Haskellは言いたいことがあります。まず、Haskellでの並列プログラミングと並行プログラミングに関する Simon Peyton Jonesのチュートリアルをご覧ください


「高速のネイティブコードコンパイラとともに」?
JD

donsはGHCIを指していると思います。
グレゴリーヒグリー

3
@ジョン:shootout.alioth.debian.org/u32/…たとえば、Haskellはシュートアウトでかなりうまくいきます。
ピーカー、

4
@ジョン:シュートアウトコードは非常に古く、遠い昔からGHCは最適化コンパイラではありませんでした。それでも、必要に応じて、Haskellコード低レベルになってパフォーマンスを発揮できることを証明しています。銃撃戦の新しいソリューションは、より慣用的でありながら高速です。
ピーカー、2011年

1
@GregoryHigley GHCIとGHCには違いがあります。
ジェレミーリスト14


18

私は昨年、Haskellを学び、その中でかなり大きく複雑なプロジェクトを書いてきました。(プロジェクトは自動化されたオプション取引システムであり、取引アルゴリズムから低レベルの高速市場データフィードの解析と処理に至るまですべてがHaskellで行われます。)非常に簡潔で理解しやすい(適切な背景)Javaバージョンよりも、非常に堅牢です。

おそらく、私にとって最大のメリットは、モノイド、モナドなどの制御フローをモジュール化できることでした。非常に簡単な例は、Orderingモノイドです。次のような表現で

c1 `mappend` c2 `mappend` c3

どこc1とそのリターンにLTEQまたはGTc1返却はEQ評価し、式が継続する原因となりますc2。がc2返されるLTGT、それが全体の値である場合、およびc3が評価されない場合。この種のことは、モナディックメッセージジェネレーターやパーサーなど、さまざまなタイプの状態を持ち歩いている、さまざまなアボート条件を持っている、またはアボートが本当に意味するかどうか特定の呼び出しを決定できるようにしたい場合に、かなり高度で複雑になります「これ以上処理しない」または「最後にエラーを返すが、さらにエラーメッセージを収集する処理を続行する」という意味です。

これはすべて、習得に時間がかかり、おそらくかなりの努力が必要なものであるため、これらの手法をまだ知らない人にとっては、説得力のある議論をするのは難しい場合があります。All About Monadsチュートリアルは、この1つの側面のかなり印象的なデモンストレーションを提供すると思いますが、この資料に精通していない人が最初または3番目の注意深い読書でさえ「理解する」とは思わないでしょう。

とにかく、Haskellには他にも多くの優れた機能がありますが、これはあまり複雑ではないため、あまり言及されていない主要な機能です。


2
とても興味深い!合計何行のHaskellコードが自動取引システムに入りましたか?フォールトトレランスをどのように処理し、どのようなパフォーマンス結果を得ましたか?最近私はHaskellが低レイテンシプログラミングに適している可能性があると考えていました...
JD

12

興味深い例については、http//en.literateprograms.org/Quicksort_(Haskell)をご覧ください。

興味深いのは、さまざまな言語での実装を見ることです。

Haskellを他の関数型言語と一緒に非常に興味深いものにしているのは、プログラミングの方法について異なる考え方をしなければならないという事実です。たとえば、通常、forループやwhileループは使用しませんが、再帰を使用します。

上記のように、Haskellや他の関数型言語は、並列処理やマルチコアで動作するアプリケーションの作成に優れています。


2
再帰は爆弾です。それとパターンマッチング。
Ellery Newcomer

1
関数型言語で書く場合、forループとwhileループを取り除くのは私にとって最も難しい部分です。:)
James Black

4
ループの代わりに再帰で考えることを学ぶことは、私にとっても最も困難な部分でした。それがようやく沈んだとき、それは私がこれまでに持っていた最高のプログラミングのひらめきの1つでした。
Chris Connett 2009

8
動作しているHaskellプログラマがプリミティブな再帰を使用することはほとんどありません。ほとんどの場合、マップやフォルダなどのライブラリ関数を使用します。
ポールジョンソン

18
Hoareのオリジナルのクイックソートアルゴリズムがこの場違いなリストベースの形式に粗雑化され、役に立たない非効率的な実装がHaskellで「エレガントに」記述できるようになったことは、さらに興味深いと思います。実際の(インプレースの)クイックソートをHaskellで書こうとすると、それは地獄のように醜いことがわかります。Haskellで競争力のあるジェネリッククイックソートを作成しようとすると、GHCのガベージコレクターの長期にわたるバグが原因で実際​​には不可能であることがわかります。Haskellの乞食の信念であるIMHOの良い例として、クイックソートを呼ぶ。
JD、

8

例をあげることはできませんが、私はOCamlの男ですが、自分のような状況にいるときは、好奇心が効くだけなので、コンパイラー/インタープリターをダウンロードして実行する必要があります。おそらく、特定の関数型言語の長所と短所について、はるかに多くのことを学ぶでしょう。


1
コンパイラのソースコードを読むことを忘れないでください。それはまたあなたに多くの貴重な情報を与えるでしょう。
JD

7

アルゴリズムや数学の問題を処理するときに私がとてもクールだと思うことの1つは、Haskell固有の計算の遅延評価です。これは、その厳密な機能的性質のためにのみ可能です。

たとえば、すべての素数を計算したい場合は、

primes = sieve [2..]
    where sieve (p:xs) = p : sieve [x | x<-xs, x `mod` p /= 0]

結果は実際には無限のリストです。しかし、Haskellはそれを左から右に評価するので、リスト全体を必要とする何かを実行しようとしない限り、プログラムを無限に動かさずに次のように使用できます。

foo = sum $ takeWhile (<100) primes

これは、すべての素数が100未満の合計です。これは、いくつかの理由で便利です。まず、すべての素数を生成する1つの素数関数を作成するだけで、素数を操作する準備がほぼ整います。オブジェクト指向プログラミング言語では、返す前に計算する素数を関数に通知する方法、またはオブジェクトで無限リストの動作をエミュレートする方法が必要です。もう1つは、一般に、計算する対象を表現するコードであり、評価の順序ではなく、コンパイラーが代わりに行うコードです。

これは無限リストに役立つだけでなく、実際には、必要以上に評価する必要がない場合は、いつでも知らなくても使用されます。


2
これは完全に正しいわけではありません。C#(オブジェクト指向言語)のイールドリターン動作を使用すると、オンデマンドで評価される無限リストを宣言することもできます。
ジェフイエーツ

2
いい視点ね。あなたは正しいので、他の言語でできることとできないことを断定的に述べることは避けるべきです。私の例には欠陥があると思いますが、それでもHaskellの遅延評価の方法から何かを得ていると思います。それは、デフォルトで実際にあり、プログラマーの努力なしです。そしてこれは、その機能的な性質と副作用がないためだと思います。
ワックスウィング2009

8
あなたは、「ふるい」がエラトステネスのふるいではない理由を読むことに興味があるかもしれませんlambda-the-ultimate.org/node/3127
Chris Conway

@クリス:ありがとう、それは実際にはかなり興味深い記事でした!上記の素数関数は非常に遅いため、私が自分の計算に使用しているものではありません。それにもかかわらず、記事はmodのすべての数値をチェックすることが実際には異なるアルゴリズムであるという良い点を持ち出します。
2009

6

いくつかの小さな例を見ることはHaskellを自慢するための最良の方法ではないことを他の人に同意します。しかし、とにかくいくつかあげます。オイラープロジェクトの問題18および67に対する超高速の解決策を次に示します。これらは、三角形の底辺から頂点までの最大合計パスを見つけるように求めます。

bottomUp :: (Ord a, Num a) => [[a]] -> a
bottomUp = head . bu
  where bu [bottom]     = bottom
        bu (row : base) = merge row $ bu base
        merge [] [_] = []
        merge (x:xs) (y1:y2:ys) = x + max y1 y2 : merge xs (y2:ys)

これは、LeshとMitzenmacherによるBubbleSearchアルゴリズムの完全で再利用可能な実装です。大きなメディアファイルを無駄なくDVDにアーカイブストレージにパックするために使用しました。

data BubbleResult i o = BubbleResult { bestResult :: o
                                     , result :: o
                                     , leftoverRandoms :: [Double]
                                     }
bubbleSearch :: (Ord result) =>
                ([a] -> result) ->       -- greedy search algorithm
                Double ->                -- probability
                [a] ->                   -- list of items to be searched
                [Double] ->              -- list of random numbers
                [BubbleResult a result]  -- monotone list of results
bubbleSearch search p startOrder rs = bubble startOrder rs
    where bubble order rs = BubbleResult answer answer rs : walk tries
            where answer = search order
                  tries  = perturbations p order rs
                  walk ((order, rs) : rest) =
                      if result > answer then bubble order rs
                      else BubbleResult answer result rs : walk rest
                    where result = search order

perturbations :: Double -> [a] -> [Double] -> [([a], [Double])]
perturbations p xs rs = xr' : perturbations p xs (snd xr')
    where xr' = perturb xs rs
          perturb :: [a] -> [Double] -> ([a], [Double])
          perturb xs rs = shift_all p [] xs rs

shift_all p new' [] rs = (reverse new', rs)
shift_all p new' old rs = shift_one new' old rs (shift_all p)
  where shift_one :: [a] -> [a] -> [Double] -> ([a]->[a]->[Double]->b) -> b
        shift_one new' xs rs k = shift new' [] xs rs
          where shift new' prev' [x] rs = k (x:new') (reverse prev') rs
                shift new' prev' (x:xs) (r:rs) 
                    | r <= p    = k (x:new') (prev' `revApp` xs) rs
                    | otherwise = shift new' (x:prev') xs rs
                revApp xs ys = foldl (flip (:)) ys xs

このコードはランダムに意味不明なもののように見えます。しかし、Mitzenmacherのブログエントリを読んでアルゴリズムを理解すると、検索対象について何も言わずにアルゴリズムをコードにパッケージ化できることに驚くことでしょう。

あなたが要求したようにあなたにいくつかの例を与えたので、私は言うでしょう Haskellのを鑑賞するために開始するための最良の方法は:私がDVDパッカー書き込むために必要なアイデアた紙読み取ることがあるのはなぜ関数型プログラミング事項をジョン・ヒューズによってを。この論文は実際にはHaskellよりも古いものですが、Haskellのような人々のアイデアのいくつかを見事に説明しています。


5

私にとって、Haskellの魅力は、コンパイラが保証する正確さの約束です。それがコードの純粋な部分であっても。

私はたくさんの科学シミュレーションコードを書いており、以前のコードにバグがあり、現在の作業の多くを無効にする可能性があるかどうか何度も疑問に思っています。


6
正確さをどのように保証しますか?
ジョナサンフィショフ2010

コードの純粋な部分は、純粋でない部分よりもはるかに安全です。投資の信頼/努力のレベルははるかに高いです。
rpg

1
何があなたにその印象を与えましたか?
JD

5

特定のタスクについては、Haskellを使用すると非常に生産的であることがわかりました。

その理由は、簡潔な構文とテストの容易さのためです。

関数宣言の構文は次のようになります。

foo a = a + 5

これが、関数の定義について考えることができる最も簡単な方法です。

逆を書けば

inverseFoo a = a-5

次のように書くことで、ランダム入力の逆であることを確認できます

prop_IsInverse :: Double-> Bool
prop_IsInverse a = a ==(inverseFoo $ foo a)

そしてコマンドラインから呼び出す

jonny @ ubuntu:runhaskell quickCheck + names fooFileName.hs

入力をランダムに100回テストすることにより、ファイル内のすべてのプロパティが保持されていることを確認します。

Haskellがすべてに最適な言語だとは思いませんが、小さな関数の記述やテストに関しては、これ以上良いものはありません。プログラミングに数学的な要素がある場合、これは非常に重要です。


どのような問題を解決し、他のどの言語を試しましたか?
JD

1
モバイルおよびiPad用のリアルタイム3Dグラフィック。
ジョナサンフィショフ2010年

3

Haskellの型システムに頭を抱えることができれば、それ自体はかなりの成果だと思います。


1
何がありますか?必要であれば、 "data" == "class"および "typeclass" = "interface" / "role" / "trait"と考えてください。それはもっと簡単なことではありません。(あなたを台無しにする "null"さえありません
。nullは

8
jrockwayには、やるべきことがたくさんあります。あなたと私はそれが比較的簡単であると感じていますが、多くの人々-多くの開発者でさえ-ある種の抽象化を理解するのは非常に難しいと感じています。日常的に使用しているにもかかわらず、より主流の言語でのポインターや参照の概念をまだ理解していない多くの開発者を知っています。
グレゴリーヒグリー


1

関数型プログラミングは、脳をねじ曲げてプログラミングを別の角度から見るようになると言った人にも同意します。趣味で使っただけですが、問題への取り組み方が根本的に変わったと思います。私は、Haskellに触れずに(そしてPythonでジェネレーターとリスト内包表記を使用して)いない限り、LINQでほとんど効果がなかったと思います。


-1

逆説的な見方をすると、Steve YeggeはHindely-Milner言語には優れたシステムを作成するために必要な柔軟性がないと書いています

HMは、まったく役に立たない正式な数学的な意味で非常にきれいです。いくつかの計算構造を非常にうまく処理します。Haskell、SML、OCamlにあるパターンマッチングディスパッチは特に便利です。当然のことながら、それは他のいくつかの一般的で非常に望ましい構成要素をせいぜいぎこちなく処理しますが、それらは、あなたが誤っている、実際には望んでいないと言って、それらのシナリオを説明しています。ああ、変数を設定するようなものです。

Haskellは学ぶ価値がありますが、独自の弱点があります。


5
確かに、強い型システムでは一般にそれらを遵守する必要があることは確かです(それがその強みを役立たせるものです)が、多くの(ほとんどの)既存のHMベースの型システムが実際にある種の 'リンクで説明されているように「ハッチをエスケープする」(O'CamlのObj.magicを例にとりますが、ハックとして以外は使用していません)。ただし、実際には、多くの種類のプログラムでは、そのようなデバイスは必要ありません。
ザックスノー

3
変数の設定が「望ましい」かどうかの問題は、代替構成体を使用することがどれほどの苦痛であるか、変数の使用によってどれだけの苦痛が生じるかによって異なります。それはすべての議論を却下するのではなく、公理として「変数は非常に望ましい構成体である」という文をとることは説得力のある議論の基礎ではないことを指摘します。それはたまたま、ほとんどの人がプログラミングの方法を学ぶ方法です。
gtd 2009

5
-1:スティーブの発言は部分的に古くなっていますが、ほとんど完全に事実に反しています。OCamlの緩和された値の制限と.NETの型システムは、彼のステートメントに対するいくつかの明白な反例です。
JD

4
Steve Yeggeは、静的型付けについてボンネットに不当な働きをしています。そして、彼がそれについて間違っていると言っていることのほとんどであるだけでなく、利用可能なすべての機会(および一部の利用できない機会)でもそれを持ち続けています。この点については、自分の経験だけを信頼することをお勧めします。
ShreevatsaR

3
静的型と動的型ではYeggeに同意しませんが、HaskellにはData.Dynamic型があります。動的なタイピングが必要な場合は、それを使用できます!
jrockway 2009年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.