Frage

Ich habe diesen Code, den ich Punktfrei machen möchte.

(\k t -> chr $ a + flip mod 26 (ord k + ord t -2*a))

Wie mache ich das?

Gibt es auch einige allgemeine Regeln für den punktfreien Stil als "Denken Sie an diese AMD, die Sie etwas einfallen lassen"?

War es hilfreich?

Lösung

Eine Funktion drehen

func x y z = (some expression in x, y and z)

In punktfreier Form versuche ich im Allgemeinen zu folgen, was mit dem letzten Parameter getan wird z und schreiben die Funktion als

func x y z = (some function pipeline built using x and y) z

Dann kann ich das stornieren zs zu bekommen

func x y = (some function pipeline built using x and y)

Dann sollte das Wiederholen des Vorgangs für y und x enden func in punktfreier Form. Eine wesentliche Transformation, die in diesem Prozess erkennen kann, ist:

    f z = foo $ bar z    -- or f z = foo (bar z)
<=> f z = foo . bar $ z
<=> f   = foo . bar

Es ist auch wichtig, sich daran zu erinnern, dass Sie mit teilweise Bewertung das letzte Argument für eine Funktion "abbrechen" können:

foo $ bar x y == foo . bar x $ y    -- foo applied to ((bar x) applied to y)

Betrachten Sie für Ihre spezielle Funktion den Fluss, der k und t durchgehen:

  1. Sich bewerben ord zu jedem von ihnen
  2. Fügen Sie die Ergebnisse hinzu
  3. Subtrahieren 2*a
  4. Nehmen Sie das Ergebnis Mod 26
  5. Füge hinzu ein
  6. Sich bewerben chr

Als erster Versuch, zu vereinfachen, bekommen wir:

func k t = chr . (+a) . (`mod` 26) . subtract (2*a) $ ord k + ord t

Beachten Sie, dass Sie vermeiden können flip durch Verwendung eines Abschnitts auf mod, und Abschnitte verwenden - Werden Sie in Haskell chaotisch, also gibt es eine subtract Funktion (sie stießen mit der Syntax zum Schreiben negativer Zahlen zusammen: (-2) bedeutet negativ 2 und ist nicht dasselbe wie subtract 2).

In dieser Funktion, ord k + ord t ist ein ausgezeichneter Kandidat für die Verwendung Data.Function.on (Verknüpfung). Mit diesem nützlichen Kombinator können wir ersetzen ord k + ord t mit einer Funktion auf k und t:

func k t = chr . (+a) . (`mod` 26) . subtract (2*a) $ ((+) `on` ord) k t

Wir sind jetzt sehr kurz davor

func k t = (function pipeline) k t

und daher

func = (function pipeline)

Leider ist Haskell ein bisschen chaotisch, wenn es darum geht, eine binäre Funktion mit einer Abfolge von Unary -Funktionen zu komponieren, aber es gibt einen Trick (ich werde sehen, ob ich eine gute Referenz dafür finden kann), und wir haben am Ende:

import Data.Function (on)

func = ((chr . (+a) . (`mod` 26) . subtract (2*a)) .) . ((+) `on` ord)

Das ist fast eine schöne punktfreie Funktionspipeline, bis auf diesen hässlichen Komponiertrick. Durch Definition des .: Der Betreiber schlug in den Kommentaren vor auf dieser Seite, Dies ist ein wenig zu:

import Data.Function (on)

(.:) = (.).(.)

func = (chr . (+a) . (`mod` 26) . subtract (2*a)) .: ((+) `on` ord)

Um dies noch mehr zu polieren, können Sie einige Helferfunktionen hinzufügen, um die Buchstabenverwandlung von der Buchstabe zu trennen Caesar -Chiffre Arithmetik. Zum Beispiel: letterToInt = subtract a . ord

Andere Tipps

Gibt es auch einige allgemeine Regeln für den punktfreien Stil als "Denken Sie an diese AMD, die Sie etwas einfallen lassen"?

Sie können immer das "PL" -Tool von lambdabot betrügen und verwenden (entweder durch #haskell auf Freenode oder mit EG GHCI auf Säure). Für Ihren Code gibt pl::

((chr . (a +) . flip mod 26) .) . flip flip (2 * a) . ((-) .) . (. ord) . (+) . ord

Was keine Verbesserung ist, wenn Sie mich fragen.

Es gibt definitiv eine Reihe von Tricks, um einen Ausdruck in einen punktfreien Stil umzuwandeln. Ich behaupte nicht, ein Experte zu sein, aber hier sind einige Tipps.

Zunächst möchten Sie die Funktionsargumente im rechtsten Term des Ausdrucks isolieren. Ihre Hauptwerkzeuge hier sind flip und $, mit den Regeln:

f a b ==> flip f b a
f (g a) ==> f $ g a

wo f und g sind Funktionen und a und b sind Ausdrücke. Also um anzufangen:

(\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))

Jetzt müssen wir bekommen t auf der rechten Seite. Verwenden Sie dazu die Regel:

f (g a) ==> (f . g) a

Und so:

-- 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)

Jetzt müssen wir alles links von drehen k und t in einen großen Funktionsterm, damit wir einen Ausdruck der Form haben (\k t -> f k t). Hier werden die Dinge ein bisschen umwerfend. Beachten Sie zunächst, dass alle Begriffe bis zum letzten Mal $ sind Funktionen mit einem einzigen Argument, sodass wir sie komponieren können:

(\k t -> chr . (a +) . flip mod 26 . flip (-) (2*a) $ ((. ord) . ((+) . ord)) k t)

Jetzt haben wir eine Funktion des Typs Char -> Char -> Int dass wir mit einer Funktion des Typs komponieren wollen Int -> Char, eine Funktion des Typs ergeben Char -> Char -> Char. Wir können das mit der (sehr seltsam aussehenden) Regel erreichen

f (g a b) ==> ((f .) . g) a b

Das gibt uns:

(\k t -> (((chr . (a +) . flip mod 26 . flip (-) (2*a)) .) . ((. ord) . ((+) . ord))) k t)

Jetzt können wir nur eine Beta -Reduktion anwenden:

((chr . (a +) . flip mod 26) .) . (flip flip (2*a) . ((-) . ) . ((. ord) . (+) .ord))

Ich gehe davon aus, dass der Punkt Ihrer Punktfreiheit darin besteht, den Code prägnanter und lesbarer zu gestalten. Ich denke daher, dass es ratsam ist, auch einige andere Refaktorings zur Vereinfachung durchzuführen, die dann das Entfernen der Variablen erleichtern könnten.

(\k t -> chr $ a + flip mod 26 (ord k + ord t - 2*a))

Zunächst die flip ist unnötig:

(\k t -> chr $ a + (ord k + ord t - 2*a) `mod` 26)

Als nächstes würde ich verwenden Name und Eroberung Um eine unabhängig verwendbare Unterfunktion zu berücksichtigen:

encode_characters k t = chr $ encode (ord k) (ord t)
encode x y = (x + y - 2*a) `mod` 26 + a

Ich gab dem ersten Ausdruck auch einen Namen, um es klarer und wiederverwendbar zu machen. encode_characters ist jetzt einfach mit der Technik von @NeFrubyr mit der Technik von Punkten zu machen:

encode_characters = chr . encode `on` ord

Was den zweiten Ausdruck betrifft, kann ich keine Form erstellen, die lesbarer ist als alle in den anderen Antworten gezeigt und alle weniger lesbar als die punktuelle Form. Ich würde daher vorschlagen, an dieser Stelle die Wiederaufnahme des Refactors zu stoppen und die Sauberkeit und Wiederverwendbarkeit des resultierenden Code zu bewundern.

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

PS: In Abhängigkeit vom Kontext des Problems kann durch eine geringfügige Änderung der Funktionsoberflächen (welche Daten in welcher Form in die Funktionen übergeben werden) durch Verallgemeinerung des Problems zu vereinfachen.

A. Die Funktion implementieren und vereinfachen encode_n_characters :: [Char] -> Char wo encode_characters k t = encode_n_characters [k, t]. Ist das Ergebnis einfacher als die spezielle Zwei-Argument-Funktion?

B. Implementieren Sie eine Funktion encode' definiert über encode' (x + y) = encode x y und Neuauflagen encode_characters Verwenden dieser Funktion. Wird eine der Funktionen einfacher? Ist die Implementierung insgesamt einfacher? Ist encode' mehr oder weniger wiederverwendbar als encode?

Verbinden IRC, #Haskell, und Fragen Sie Lambdabot!:

<you> @pl (\k t -> chr $ a + flip mod 26 (ord k + ord t -2*a))
<lambdabot> [the answer]
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top