Haskell でのポイントフリー
-
20-09-2019 - |
質問
ポイントフリーにしたいコードがあります。
(\k t -> chr $ a + flip mod 26 (ord k + ord t -2*a))
それ、どうやったら出来るの?
また、ポイントフリースタイルには、「これについて考えて何かを思いつく」以外の一般的なルールはありますか?
解決
機能を切り替えるには
func x y z = (some expression in x, y and z)
ポイントフリー形式に変換するには、通常、最後のパラメータに対して行われた処理に従うようにします。 z
そして関数を次のように書きます
func x y z = (some function pipeline built using x and y) z
そうすればキャンセルできます z
取得する
func x y = (some function pipeline built using x and y)
次に、y と x に対してこのプロセスを繰り返すと、次のようになります。 func
ポイントフリー形式で。このプロセスで認識すべき重要な変換は次のとおりです。
f z = foo $ bar z -- or f z = foo (bar z)
<=> f z = foo . bar $ z
<=> f = foo . bar
部分評価を使用すると、関数の最後の引数を「切り離す」ことができることを覚えておくことも重要です。
foo $ bar x y == foo . bar x $ y -- foo applied to ((bar x) applied to y)
特定の機能については、次のようなフローを考慮してください。 k
そして t
通過します:
- 適用する
ord
それぞれに - 結果を追加
- 2*a を引く
- 結果を mod 26 として取得します
- 追加
- 適用する
chr
したがって、単純化の最初の試みとして、次のようになります。
func k t = chr . (+a) . (`mod` 26) . subtract (2*a) $ ord k + ord t
回避できることに注意してください flip
のセクションを使用して mod
, 、およびを使用しているセクション -
Haskell では面倒になるので、 subtract
関数 (負の数を記述するための構文と衝突します。 (-2)
は負の 2 を意味し、次と同じではありません。 subtract 2
).
この関数では、 ord k + ord t
を使用するのに最適な候補です Data.Function.on
(リンク)。この便利なコンビネータを使用すると、 ord k + ord t
関数が適用されたもの k
そして t
:
func k t = chr . (+a) . (`mod` 26) . subtract (2*a) $ ((+) `on` ord) k t
今では実現に非常に近づいています
func k t = (function pipeline) k t
それゆえ
func = (function pipeline)
残念ながら、Haskell は、一連の単項関数を使用して二項関数を構成する場合、少し面倒ですが、コツがあります (良いリファレンスが見つかるかどうか確認してみます)。最終的には次のようになります。
import Data.Function (on)
func = ((chr . (+a) . (`mod` 26) . subtract (2*a)) .) . ((+) `on` ord)
これは、醜い合成トリックを除けば、ほぼ素晴らしい、ポイントフリーの関数パイプラインです。を定義することで、 .:
コメントで提案された演算子 このページで, これにより、次のように少し整理されます。
import Data.Function (on)
(.:) = (.).(.)
func = (chr . (+a) . (`mod` 26) . subtract (2*a)) .: ((+) `on` ord)
これをさらに洗練させるために、文字 <-> Int 変換を文字変換から分離するヘルパー関数を追加できます。 シーザー暗号 算術。例えば: letterToInt = subtract a . ord
他のヒント
また、ポイントフリースタイルには、「これについて考えて何かを思いつく」以外の一般的なルールはありますか?
lambdabot からいつでもチートして「pl」ツールを使用できます (freenode の #haskell に移動するか、たとえば 酸性ghci)。あなたのコードの場合、plは次のようになります:
((chr . (a +) . flip mod 26) .) . flip flip (2 * a) . ((-) .) . (. ord) . (+) . ord
私に言わせれば、それは実際の改善ではありません。
ポイントフリースタイルへの発現を変換するトリックのセットは間違いなくあります。私は専門家であることを主張しませんが、ここではいくつかのヒントがあります。
まず、あなたは表現の一番右の用語で関数の引数を分離します。ここにあなたの主なツールは、ルールを使用して、flip
と$
になります:
f a b ==> flip f b a
f (g a) ==> f $ g a
ここでf
とg
は関数であり、a
とb
は式です。だから、開始します:
(\k t -> chr $ a + flip mod 26 (ord k + ord t -2*a))
-- replace parens with ($)
(\k t -> chr $ (a +) . flip mod 26 $ ord k + ord t - 2*a)
-- prefix and flip (-)
(\k t -> chr $ (a +) . flip mod 26 $ flip (-) (2*a) $ ord k + ord t)
-- prefix (+)
(\k t -> chr $ (a +) . flip mod 26 $ flip (-) (2*a) $ (+) (ord k) (ord t))
は、今、私たちは右側にt
を取得する必要があります。これを行うには、ルールを使用します:
f (g a) ==> (f . g) a
ですからます:
-- pull the t out on the rhs
(\k t -> chr $ (a +) . flip mod 26 $ flip (-) (2*a) $ ((+) (ord k) . ord) t)
-- flip (.) (using a section)
(\k t -> chr $ (a +) . flip mod 26 $ flip (-) (2*a) $ ((. ord) $ (+) (ord k)) t)
-- pull the k out
(\k t -> chr $ (a +) . flip mod 26 $ flip (-) (2*a) $ ((. ord) . ((+) . ord)) k t)
さて、我々は、フォームのk
の発現を持っているように、一つの大きな機能の項にt
と(\k t -> f k t)
の左側にすべてをオンにする必要があります。物事はビット心曲げを取得する場所です。最後$
までのすべての用語は、単一の引数を持つ関数であることに注意してください、私たちはそれらを構成することができ、で開始するには:
(\k t -> chr . (a +) . flip mod 26 . flip (-) (2*a) $ ((. ord) . ((+) . ord)) k t)
さて、我々はタイプChar -> Char -> Int
の機能をもたらす、型Int -> Char
の機能を構成したいタイプChar -> Char -> Char
の機能を持っています。私たちは、
f (g a b) ==> ((f .) . g) a b
これは私たちを与えます:
(\k t -> (((chr . (a +) . flip mod 26 . flip (-) (2*a)) .) . ((. ord) . ((+) . ord))) k t)
今、私たちはベータ版の削減を適用することができます:
((chr . (a +) . flip mod 26) .) . (flip flip (2*a) . ((-) . ) . ((. ord) . (+) .ord))
私はあなたのポイント・解放のポイントは、コードをより簡潔で読みやすくするためであると仮定しています。従って私は、また、それが簡単に変数を削除することになるかもしれない簡素化に向けたいくつかの他のリファクタリングを行うことが賢明だと思います。
(\k t -> chr $ a + flip mod 26 (ord k + ord t - 2*a))
すべてのまず、flip
は不要です。
(\k t -> chr $ a + (ord k + ord t - 2*a) `mod` 26)
次に、私はの名前を使用すると統治は独立して使用可能なサブ機能を考慮します:
encode_characters k t = chr $ encode (ord k) (ord t)
encode x y = (x + y - 2*a) `mod` 26 + a
私もそれがより明確にし、再利用可能にするために、最初の式に名前を与えました。 encode_characters
は今@Nefrubyrから技術を使用して、ポイントを無料にするのは簡単です。
encode_characters = chr . encode `on` ord
第二の発現に関しては、私は他の回答に示されており、彼らはすべての点別のフォームより読みにくくしているよりも読みやすいですフォームを生成することはできません。従って私はこの時点でリファクタリングを停止することをお勧めし、得られたコードの清潔さと再利用性を賞賛します。
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~
PSは:運動として、問題の状況に応じて、機能インタフェースのいくつかのわずかな修正は(どのようなデータをどのような形では、関数に渡された)問題を一般化することによって、より単純化をもたらす可能性がある
。 A。関数encode_n_characters :: [Char] -> Char
encode_characters k t = encode_n_characters [k, t]
を実装し、簡素化します。専門的な2つの引数の関数よりも結果に簡単ですか?
B。 encode'
経由で定義された関数encode' (x + y) = encode x y
を実装し、この機能を使用してencode_characters
再実装。機能のいずれかが簡単になるのか?全体的な実装に簡単ですか?多かれ少なかれ、再利用可能なencode'
よりencode
されていますか。
接続してください IRC、#haskell, 、 そして ラムダボットに聞いてください!:
<you> @pl (\k t -> chr $ a + flip mod 26 (ord k + ord t -2*a))
<lambdabot> [the answer]