1. ホーム
  2. haskell

[解決済み] なぜHaskellは "Best Imperative Language "と呼ばれるのか?

2023-04-30 09:29:51

疑問点

(この質問がオントピックであることを望みます -- 私は答えを検索しようとしましたが、決定的な答えを見つけられませんでした。もしこれがオフトピックであったり、すでに回答されていたりする場合は、モデレート/削除してください)。

私は、Haskell が 最高の命令型言語 これはもちろん、Haskell が通常その 関数型 機能でよく知られているからです。

そこで質問ですが、Haskellのどのような品質や特徴が、Haskellがとみなされることを正当化する理由となるのでしょうか? 最高の命令型言語 -- それとも冗談のようなものでしょうか?

どのように解決するのですか?

私は半分真実だと考えています。 Haskellは素晴らしい抽象化能力を持っていて、それは命令的な考えに対する抽象化も含んでいます。 例えば、Haskellには命令的なwhileループは組み込まれていませんが、それを書けば、今はそうなっています。

while :: (Monad m) => m Bool -> m () -> m ()
while cond action = do
    c <- cond
    if c 
        then action >> while cond action
        else return ()

このレベルの抽象化は多くの命令型言語では困難です。 PythonやC#のようなクロージャを持つ命令型言語では可能です。

しかし、Haskellは(非常にユニークな)次のような能力も持っています。 許容される副作用を特徴づける モナドクラスを使用します。 たとえば、ある関数があるとします。

foo :: (MonadWriter [String] m) => m Int

これは "imperative"関数である可能性がありますが、2つのことしかできないことが分かっています。

  • 文字列のストリームを出力する。
  • Intを返す

コンソールへのプリントやネットワーク接続の確立などはできません。 抽象化能力と組み合わせることで、"ストリームを生成するあらゆる計算"に作用する関数などを書くことができます。

Haskellが非常に優れた命令型言語であるのは、本当にHaskellの抽象化能力によるものなのです。

しかし、その半面、構文に問題があります。 Haskellはかなり冗長で、命令文スタイルで使うには厄介だと思います。 以下は、上記を使った命令型スタイルの計算の例です。 while ループを使って、リンクリストの最後の要素を見つける命令型スタイルの計算の例です。

lastElt :: [a] -> IO a
lastElt [] = fail "Empty list!!"
lastElt xs = do
    lst <- newIORef xs
    ret <- newIORef (head xs)
    while (not . null <$> readIORef lst) $ do
        (x:xs) <- readIORef lst
        writeIORef lst xs
        writeIORef ret x
    readIORef ret

IORef のゴミ、二重読み込み、読み込みの結果をバインドしなければならないこと、fmapping ( <$> ) を使ってインライン計算の結果を操作しなければならない......すべてが非常に複雑に見えます。 からは全く意味がありません。 機能的 の観点からは非常に理にかなっていますが、命令型言語では使いやすくするためにこれらの詳細のほとんどを隠してしまう傾向があります。

確かに、もし私たちが別の while -スタイルのコンビネータを使用すれば、よりきれいになることは確かです。 しかし、その哲学を十分に発揮すれば(豊富なコンビネータを使って自分自身を明確に表現すれば)、再び関数型プログラミングにたどり着きます。 命令型スタイルのHaskellは、よく設計された命令型言語(たとえばpython)のように、quot;flow"ではないだけです。

結論として、Haskellは構文を一新することで、最高の命令型言語となる可能性があります。 しかし、フェイスリフトの性質上、それは内部的に美しく本当のものを、外部的に美しく偽のものに置き換えることになります。

EDIT : コントラスト lastElt をこのpythonの音訳と比較してみてください。

def last_elt(xs):
    assert xs, "Empty list!!"
    lst = xs
    ret = xs.head
    while lst:
        ret = lst.head
        lst = lst.tail
    return ret 

同じ行数ですが、各行のノイズはかなり少なくなっています。


編集2

参考までに、このように ピュア の置換がどのように見えるかです。

lastElt = return . last

これで終わりです。あるいは、もしあなたが、私に Prelude.last :

lastElt [] = fail "Unsafe lastElt called on empty list"
lastElt [x] = return x
lastElt (_:xs) = lastElt xs

あるいは、任意の Foldable のデータ構造で動作させ、実際には は必要ありません。 IO を使用して、エラーを処理します。

import Data.Foldable (Foldable, foldMap)
import Data.Monoid (Monoid(..), Last(..))

lastElt :: (Foldable t) => t a -> Maybe a
lastElt = getLast . foldMap (Last . Just)

Map のように、例えば

λ➔ let example = fromList [(10, "spam"), (50, "eggs"), (20, "ham")] :: Map Int String
λ➔ lastElt example
Just "eggs"

(.) 演算子は 関数構成 .