Haskellでは、構文が従来の言語の構文とは非常に異なるため、すべてが異なります。 しかし、実際には、違いはそれほど大きくありません-あなたは正しい角度を見る必要があります。 ここに、ほとんどの部分が正しくない、そしてできればpythonistsによる解釈のための便利なガイドがあります ( 著者は「Pythonista」という言葉を使用します-ほぼ翻訳者 )Haskellコード。 最後に、次の部分を理解できるようになります(コードの一部は省略記号の後に省略されています)。
runCommand env cmd state = ... retrieveState = ... saveState state = ... main :: IO () main = do args <- getArgs let (actions, nonOptions, errors) = getOpt Permute options args opts <- foldl (>>=) (return startOptions) actions when (null nonOptions) $ printHelp >> throw NotEnoughArguments command <- fromError $ parseCommand nonOptions currentTerm <- getCurrentTerm let env = Environment { envCurrentTerm = currentTerm , envOpts = opts } saveState =<< runCommand env command =<< retrieveState
種類
::
後のすべてを無視します(また、
type
、
class
、
instance
、および
newtype
も無視し
instance
)。 型がコードを理解するのに役立つと誓う人もいます。 あなたが完全な初心者であれば、
Int
や
String
ようなものが役立つかもしれませんが、
LayoutClass
や
MonadError
ような
LayoutClass
は
LayoutClass
で
MonadError
ません。 それらを心配しないでください。
引数
fabc
は
f(a, b, c)
変換されます。 Haskellは、括弧とコンマを省略します。 この結果、引数に括弧が必要になる場合があります:
fa (b1 + b2) c
は
f(a, b1 + b2, c)
変換されます。
ドル記号
a + b
という形式の複雑な表現は非常に一般的であり、Haskellers(Haskellersの著者-翻訳者のメモ )は角括弧を好まないため、ドル記号を使用して角括弧を回避しています:
f $ a + b
f (a + b)
と同等であり、変換されます
f(a + b)
。
$
は行末で自動的に閉じる(そして書く必要はもうありません)))))、巨大な左括弧と考えることができます!)特に、それらをネストでき、それぞれがネストレベルを作成します:
f $ gx $ hy $ a + b
は
f (gx (hy (a + b)))
と等価であり、
f(g(x,h(y,a + b))
変換されます(一部の人はこれを悪い習慣だと考えています)。
次のオプションを見ることができます:
<$>
(山括弧付き)。 これは
$
と同じと考えることができます。 また、
<*>
-コンマのふりをして、
f <$> a <*> b
は
f(a, b)
変換されます。
逆アポストロフィ
x `f` y
は
f(x,y)
変換されます。 アポストロフィの間の部分は関数であり、通常はバイナリであり、引数は左右にあります。
等しい記号
2つの値が可能です。 コードブロックの先頭では、単に関数を定義することを意味します。
doThisThing abc = ...
==> def doThisThing(a, b, c): ...
キーワードの隣で、割り当て演算子として機能さ
let
ます。
let a = b + c in ...
==> a = b + c ...
左矢印
代入演算子としても機能します:
a <- createEntry x
==>
a = createEntry(x)
等号を使用しないのはなぜですか? 魔術。 (より具体的には、
createEntry x
は副作用があります。より正確には、式が
createEntry x
であることを意味します。しかし、これはすべて魔術です。今のところ、それを無視してください。)
右矢印
すべてが複雑です。 後でそれらに戻ります。
キーワードを実行
ノイズ。 無視できます。 いくつかの情報が得られます-以下に副作用がありますが、Pythonには違いが見られません。
戻る
ノイズ。 また無視します。 (実行の制御に使用される
return
は表示されません。)
ポイント
f . g $ a + b
f . g $ a + b
は
f(g(a + b))
変換されます。 実際、Pythonプログラムでは、おそらく次のようなものが表示されます。
しかし、Haskellプログラマーは余分な変数にアレルギーがあります。x = g(a + b) y = f(x)
結合演算子と魚
=<<
、
>>=
、
<=<
および
>=>
ようなものに出くわすことができます。 単純に、これらは中間変数を取り除くためのいくつかの方法です:
doSomething >>= doSomethingElse >>= finishItUp
==> x = doSomething() y = doSomethingElse(x) finishItUp(y)
Haskellプログラマーは、特に変数に別の場所に値が割り当てられている場合は特に、別の方向にそれを行う方がきれいだと判断します。
z <- finishItUp =<< doSomethingElse =<< doSomething
==>
x = doSomething() y = doSomethingElse(x) z = finishItUp(y)
最も重要なことは、
finishItUp
、
finishItUp
、および
finishItUp
定義を見ることによって、起こっていることのリバースエンジニアリングを行うことです。これは、「魚」を「流れる」ヒントを与えます。 これを行うと
>=>
同じ方法で
<=<
および
>=>
読み取ることができます(実際、
.
などの関数を構成します)。
>>
をセミコロンとして読み取り
>>
(つまり、割り当てなし):
doSomething >> doSomethingElse
==> doSomething() doSomethingElse()
部分適用
Haskellプログラマーは関数を呼び出すが、十分な引数を渡さないことがあります。 恐らく、恐らく、彼らは他の場所で残りの議論の転送を組織した。 無視、または引数として匿名関数を受け入れる関数を探します。 一般的な容疑者:
map
、
fold
(およびその変形)、
filter
、composition operator
.
、魚演算子(
=<<
など)。 これはしばしば数値演算子で発生します:
(+3)
は
lambda x: x + 3
変換されます。
制御オペレーター
本能に頼る:これらの演算子はあなたが思ったとおりに動作します! (たとえあなたが彼らがそのように働くべきではないと思うとしても)。 したがって、次のように表示される場合:
when (x == y) $ doSomething x
、「
x
は
y
に等しいので、引数
x
を指定して
doSomething
を呼び出す」を読んでください。
実際にこれを
when(x == y, doSomething(x))
に変換できないという事実は無視してください(ここではdoSomethingが呼び出されます)。 実際には
when(x == y, lambda: doSomething x)
方が正確ですが、言語が
when
構築されるかを考慮
when
方が便利な場合があります。
if
と
case
はキーワードです。 期待どおりに機能します。
右矢印(実際に!)
右矢印は左矢印とは関係ありません。 それらはコロンと考えてください:それらは常に
case
キーワードとバックスラッシュ(ラムダを宣言します:
\x -> x
は
lambda x: x
変換されます)の近くにあります。
case
を使用したパターンマッチングは非常に便利な機能ですが、この記事では説明が困難です。 おそらく最も簡単な近似は、変数の割り当てを伴う
if..elif..else
チェーンです。
case moose of Foo xyz -> x + y * z Bar z -> z * 3
==> if isinstance(moose, Foo): x = moose.x # ! y = moose.y z = moose.z return x + y * z elif isinstance(moose, Bar): z = moose.z return z * 3 else: raise Exception("Pattern match failure!")
ラッピング
で始まるという事実によって、ラッピング関数を区別できます。 Pythonのコンテキスト管理のように機能します。
withFile "foo.txt" ReadMode $ \h -> do ...
==> with open("foo.txt", "r") as h: ...
(バックスラッシュを見つけることができます。はい、これはラムです。はい、
withFile
は関数です。はい、独自に定義できます。)
例外
throw
、
catch
、
catches
、
throwIO
、
finally
、
handle
および他のすべての同様の関数は、期待どおりに機能します。 ただし、これらはすべての機能であり、キーワードではなく、すべての結果を伴うため、これはおかしいように見えるかもしれません。 たとえば、次のとおりです。
trySomething x `catch` \(e :: IOException) -> handleError e
=== catch (trySomething x) (\(e :: IOException) -> handleError e)
==> try: trySomething(x) except IOError as e: handleError(e)
たぶん
Nothing
も表示され
Nothing
場合は、
None
と考えることができます。
isNothing x
は、
x is None
ことをチェックします。 Nothingの反対は何ですか?
Just
。 たとえば、
isJust x
は
x is not None
ことを検証します。
Just
と
None
処理に関連する多くのノイズを正しい順序で見ることができます。 最も一般的なケースの1つ:
maybe someDefault (\x -> ...) mx
==> if mx is None: x = someDefault else: x = mx ...
nullがエラーの場合の具体的なオプションは次のとおりです。
maybe (error "bad value!") (\x -> ...) x
==> if x is None: raise Exception("bad value!")
投稿
期待どおりに動作しますが、Haskellでは名前なしでフィールドを作成できます。
data NoNames = NoNames Int Int data WithNames = WithNames { firstField :: Int, secondField :: Int }
したがって、
NoNames
はPythonではtuple
(1, 2)
として表され、
WithNames
次のクラスで
WithNames
ます。
class WithNames: def __init__(self, firstField, secondField): self.firstField = firstField self.secondField = secondField
この簡単な方法では、
NoNames 2 3
(2, 3)
でブロードキャストされ、
WithNames 2 3
または
WithNames { firstField = 2, secondField = 3 }
は
WithNames(2, 3)
で
WithNames(2, 3)
ます。
フィールドは少し異なります。 最も重要なことは、Haskelersがフィールドの名前を変数の前に置くことを覚えておくことです。一方、後からフィールドを置くことに慣れていることでしょう。 したがって、
field x
は
x.field
変換され
x.field
。
x.field = 2
書き方 まあ、実際には、これを行うことはできません。 コピーできますが:
return $ x { field = 2 }
==> y = copy(x) y.field = 2 return y
または、xをデータ構造の名前(大文字で始まる)に置き換えると、ゼロから作成できます。 コピー構造のみを許可するのはなぜですか? Haskellは純粋な言語だからです。 しかし、それに注意を払ってはいけません。 ちょうど別のHaskellの癖。
リスト式
当初、彼らはミランダ・ハスケル家から来ました! 彼らはほんの数文字しか持っていません。
[ x * y | x <- xs, y <- ys, y > 2 ]
==> [ x * y for x in xs for y in ys if y > 2 ]
また、Haskelersはリスト式を複数行形式で記述することを好むことが多いことがわかります(おそらく、この方法で読みやすいと思うでしょう)。 次のようになります。
do x <- xs y <- ys guard (y > 2) return (x * y)
したがって、左矢印が表示され、サードパーティの効果が期待されるように思われない場合、これはおそらくリスト式です。
他のキャラクター
リストは、Pythonの場合と同じように機能します:
[1, 2, 3]
-実際には3つの要素のリスト。
x:xs
ようなコロンは、
x
を先頭に、
xs
を末尾に持つリストを作成することを意味します(Lispファンの場合は
cons
)
++
-リストの連結、
!!
-インデックスによる処理。 バックスラッシュは
lambda
意味します。 理解できない文字が見つかった場合は、 Hoogleで検索してみてください(はい、文字で動作します!)。
より多くのノイズ
次の関数、おそらくノイズであり、おそらく無視される可能性があります:
liftIO
、
lift
、
runX
(
runX
.
runState
)、
unX
(
unConstructor
.
unConstructor
)、
fromJust
、
fmap
、
const
、
evaluate
、引数の前の感嘆符(
f !x
) 、
seq
、
seq
記号(例:
I# x
)。
すべてをまとめる
元のコードスニペットに戻りましょう。
runCommand env cmd state = ... retrieveState = ... saveState state = ... main :: IO () main = do args <- getArgs let (actions, nonOptions, errors) = getOpt Permute options args opts <- foldl (>>=) (return startOptions) actions when (null nonOptions) $ printHelp >> throw NotEnoughArguments command <- fromError $ parseCommand nonOptions currentTerm <- getCurrentTerm let env = Environment { envCurrentTerm = currentTerm , envOpts = opts } saveState =<< runCommand env command =<< retrieveState
当て推量を使用して、この翻訳を取得できます。
def runCommand(env, cmd, state): ... def retrieveState(): ... def saveState(state): ... def main(): args = getArgs() (actions, nonOptions, errors) = getOpt(Permute(), options, args) opts = **mumble** if nonOptions is None: printHelp() raise NotEnoughArguments command = parseCommand(nonOptions) currentTerm = getCurrentTerm() env = Environment(envCurrentTerm=currentTerm, envOpts=opts) state = retrieveState() result = runCommand(env, command, state) saveState(result)
Haskellの構文を表面的に理解するのは悪くありません(畳み込みの知識を必要とする明白な方法で翻訳できない部分があります( 実際、Pythonには組み込みのreduce関数-翻訳者コメントがあります )。すべてのHaskellコードが畳み込みを扱うわけではありません。心配しすぎです!)
私が「ノイズ」と呼んだもののほとんどは、実際にはそれらの背後にある非常に深い理由があり、それらの背後にあるものに興味があるなら、Haskellで書くことを学ぶことをお勧めします。 しかし、読んでいるだけなら、これらのルールは十分すぎると思います。
PS
foldl (>>=) (return startOptions) action
本当に興味がある場合: 一連の職務パターンを実装します。 はい、地獄。
翻訳者からのPPS: Graninasはいくつかの用語の翻訳を手伝ってくれました