6

ある関数 f(x1,x2,x3,..,xN) が与えられた場合、いくつかの場所で部分的に適用すると便利なことがよくあります。たとえば、N=3 の場合、g(x)=f(1,x,3) と定義できます。しかし、Haskell の標準的な部分適用はこのようには機能せず、最初の引数を修正することによって関数を部分的に適用することしかできません (すべての関数は実際には 1 つの引数しかとらないため)。このようなことを行う簡単な方法はありますか:

g = f _ 2 _
g 1 3

の値を出力してf 1 2 3?もちろん、ラムダ関数を実行することもできます

g=(\x1 x3 -> f x1 2 x3)

しかし、これは非常に読みにくいと思います。たとえば、Mathematica では次のように動作しますが、これは非常に優れています。

g=f[#1,2,#2]&
g[1,3]

出力付きf[1,2,3]

編集:動機についてもっと何か言うべきかもしれません。このような部分的に適用された関数を、ポイント スタイルの構成、つまり次のような式で使用したいと思います。

h = g. f _ 2 . k

取得するh 3 = g(f(k(3),2))

4

5 に答える 5

3

引数の順序を変更する方法に関するこの質問を読んでから、部分適用を使用することもできますが、現在 Haskell でそれを行う最もクリーンで明確な方法は、次のとおりです。

g x y = f x 2 y
于 2014-09-20T18:07:51.487 に答える
2

その他の考慮事項:

部分適用パターンのヘルパー関数を定義します (ローカルに、または少数のパターンを数回使用している場合はグローバルに)。

fix_2 f a = \x -> f x a
fix1_3 f a b = \x -> f a x b

h = g . fix_2 f 2 . k

架空の「空白」構文ほど良くはありませんが、大丈夫です。fix_2使用中の部分適用スキームを識別するタグとして読み取ることができます1

最後の引数を修正しない部分適用スキームは必要ないことに注意してください。カリー化で 4 引数の関数の 1 番目と 3 番目の引数を修正する (2 引数の関数を残す) ことは、3 引数の関数の 1 番目と 3 番目の引数を修正することと同じです。

より複雑なアイデアとして、「アンダースコアは暗黙の関数のパラメーターである」疑似構文を実際に実装する Template Haskell quasiquoter を作成できるはずだと思います。これにより、式を次のように書くことができます。

h = g . [partial| f _ |] . k

ヘルパー関数よりも構文上のオーバーヘッドが大きく、(quasiquoter を識別するために) 追加の名前を含める必要がありますが、部分適用スキームがはるかに複雑な場合は、おそらく読みやすくなります。

h = g . [partial| f 1 _ 3 4 _ 6 |] . k

これは、Template Haskell コードを実装する一連の作業を意味しますが、私はこれまで行ったことがないので、どの程度かはわかりません。ただし、ヘルパー関数ルートではパターンごとに手動で定義する必要があるのに対し、任意の部分的なアプリケーション スキームが存在することになります。


1 2 番目の引数のみを修正すると、標準のヘルパー関数flip. 2 番目の引数に部分的に適用することは、最初の 2 つの引数を交換するように直感的には聞こえないかもしれませんが、遅延評価とカリー化のおかげで、実際には同じことになります!

于 2014-09-21T00:40:01.590 に答える
2

あなたが求めていることを行う一般的な方法はありませんが、中置セクションを代わりに使用できる場合がありますflip。あなたの最後の例を使用して:

g . (`f` 2) . k

また、関数が受け取る引数の順序を変更すると役立つ場合があることも指摘しておきます。たとえば、特に 1 つの引数に部分的に適用されることが多い関数がある場合、おそらくそれを最初の引数にする必要があります。

2D ゲーム ボードを表すデータ構造を実装するとします (チェス プログラムの場合と同様)。おそらく、getPiece関数の最初の引数をチェス ボードにし、2 番目の引数を場所にする必要があります。チェックする場所は、ボードよりも頻繁に変更される可能性が高いと思います。もちろん、これで一般的な問題が解決するわけではありませんが (ボードのリストで同じ場所を確認したい場合もあります)、問題を軽減することはできます。引数の順序を決めるとき、これが主に考慮します。

于 2014-09-20T20:44:49.790 に答える
1

いいえ、最も簡単な方法はラムダを定義することです。おそらく を試してみることができますがflip、ラムダよりもクリーンでシンプルになるとは思えません。特に引数のリストが長い場合。

于 2014-09-20T17:40:47.197 に答える
1

最も単純な (そして標準的な) 方法は、ラムダを定義することです。可能な場合は意味のある引数名を使用すると、はるかに読みやすくなります

getCurrencyData :: Date -> Date -> Currency -> IO CurrencyData
getCurrencyData fromDate toDate ccy = {- implementation goes here -}

ラムダ構文で新しい関数を定義できます

getGBPData = \from to -> getCurrencyData from to GBP

またはそれなしで

getGBPData from to = getCurrencyData from to GBP

またはコンビネータを使用できますが、これはかなり醜いと思います

getGBPData = \from to -> getCurrencyData from to GBP
           = \from to -> flip (getCurrencyData from) GBP to
           = \from    -> flip (getCurrencyData from) GBP
           = \from    -> (flip . getCurrencyData) from GBP
           = \from    -> flip (flip . getCurrencyData) GBP from
           =             flip (flip . getCurrencyData) GBP
于 2014-09-20T17:53:52.970 に答える