in Haskell libera-Point
-
20-09-2019 - |
Domanda
Ho questo codice che voglio fare gratis-point;
(\k t -> chr $ a + flip mod 26 (ord k + ord t -2*a))
Come faccio a farlo?
Inoltre ci sono alcune regole generali per stile libero punto diverso da "pensare a questo amd inventarsi qualcosa"?
Soluzione
Per attivare una funzione
func x y z = (some expression in x, y and z)
in forma libera punti, io in genere cerco di seguire ciò che viene fatto per l'ultimo parametro z
e scrivere la funzione come
func x y z = (some function pipeline built using x and y) z
Poi posso cancellare i z
s per ottenere
func x y = (some function pipeline built using x and y)
Quindi ripetere il processo per y e x devono finire con func
in forma libera-point. Una trasformazione essenziale riconoscere in questo processo è:
f z = foo $ bar z -- or f z = foo (bar z)
<=> f z = foo . bar $ z
<=> f = foo . bar
E 'anche importante ricordare che con la valutazione parziale, si può "rompere" l'ultimo argomento a una funzione:
foo $ bar x y == foo . bar x $ y -- foo applied to ((bar x) applied to y)
Per la vostra funzione particolare, considerano il flusso che k
e t
passare attraverso:
- Applica
ord
a ciascuno di essi - Aggiungi i risultati
- Sottrai 2 * a
- Prendere il risultato mod 26
- Aggiungi un
- Applica
chr
Così come un primo tentativo di semplificare, otteniamo:
func k t = chr . (+a) . (`mod` 26) . subtract (2*a) $ ord k + ord t
Si noti che è possibile evitare flip
utilizzando una sezione sul mod
, e le sezioni utilizzando -
un casino in Haskell quindi non c'è una funzione subtract
(si scontrano con la sintassi per la scrittura di numeri negativi: (-2)
significa negativo 2, e non è il stessa subtract 2
).
In questa funzione, ord k + ord t
è un ottimo candidato per l'utilizzo di Data.Function.on
( link ). Questo combinatore utile permette di sostituire ord k + ord t
con una funzione applicata a k
e t
:
func k t = chr . (+a) . (`mod` 26) . subtract (2*a) $ ((+) `on` ord) k t
Ora siamo molto vicino ad avere
func k t = (function pipeline) k t
e quindi
func = (function pipeline)
Purtroppo Haskell è un po 'disordinato, quando si tratta di comporre una funzione binaria con una successione di funzioni unari, ma c'è un trucco (Vedrò se riesco a trovare un buon riferimento per esso), e si finisce con :
import Data.Function (on)
func = ((chr . (+a) . (`mod` 26) . subtract (2*a)) .) . ((+) `on` ord)
che è quasi una bella pulita priva di punti funzione gasdotto, tranne che per quel trucco composizione brutto. Definendo l'operatore .:
suggerito nella commenti in questa pagina , questo riordina un po 'per:
import Data.Function (on)
(.:) = (.).(.)
func = (chr . (+a) . (`mod` 26) . subtract (2*a)) .: ((+) `on` ord)
Per lucidare questo un po 'di più, si potrebbe aggiungere alcune funzioni di supporto per separare la lettera <-> Int conversione dalla Cesare cifratura aritmetica. Ad esempio: letterToInt = subtract a . ord
Altri suggerimenti
Inoltre ci sono alcune regole generali per stile libero punto diverso da "pensare a questo amd inventarsi qualcosa"?
Si può sempre barare e utilizzare il "pl" strumento da lambdabot (sia andando a #haskell su freenode o utilizzando ad esempio ghci di acido ). Per il vostro codice di pl dà:
((chr . (a +) . flip mod 26) .) . flip flip (2 * a) . ((-) .) . (. ord) . (+) . ord
Il che non è in realtà un miglioramento, se mi chiedete.
C'è sicuramente una serie di trucchi per trasformare un'espressione in stile libero-point. Non pretendo di essere un esperto, ma ecco alcuni consigli.
In primo luogo, si desidera isolare gli argomenti della funzione in più a destra termine dell'espressione. I suoi principali strumenti qui saranno flip
e $
, utilizzando le regole:
f a b ==> flip f b a
f (g a) ==> f $ g a
dove f
e g
sono funzioni e a
e b
sono espressioni. Quindi, per iniziare:
(\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))
Ora abbiamo bisogno di ottenere t
sul lato destro della strada. Per fare questo, utilizzare la regola:
f (g a) ==> (f . g) a
E così:
-- 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)
Ora, abbiamo bisogno di trasformare tutto a sinistra del k
e t
in un unico grande termine funzione, in modo da avere un'espressione della forma (\k t -> f k t)
. Questo è dove le cose si fanno un po 'mind-bending. Per cominciare, si noti che tutti i termini fino all'ultimo $
sono funzioni con un singolo argomento, in modo da poterli comporre:
(\k t -> chr . (a +) . flip mod 26 . flip (-) (2*a) $ ((. ord) . ((+) . ord)) k t)
Ora, abbiamo una funzione di tipo Char -> Char -> Int
che vogliamo comporre con una funzione di tipo Int -> Char
, ottenendo una funzione di tipo Char -> Char -> Char
. Possiamo ottenere che usando la regola (molto strano-looking)
f (g a b) ==> ((f .) . g) a b
Questo ci dà:
(\k t -> (((chr . (a +) . flip mod 26 . flip (-) (2*a)) .) . ((. ord) . ((+) . ord))) k t)
Ora possiamo solo applicare una riduzione beta:
((chr . (a +) . flip mod 26) .) . (flip flip (2*a) . ((-) . ) . ((. ord) . (+) .ord))
Io parto dal presupposto che il punto del punto-liberazione è quello di rendere il codice più conciso e più leggibile. Penso quindi che sia saggio fare anche alcune altre refactoring di semplificazione che poi potrebbero rendere più facile per rimuovere le variabili.
(\k t -> chr $ a + flip mod 26 (ord k + ord t - 2*a))
Prima di tutto, il flip
non è necessaria:
(\k t -> chr $ a + (ord k + ord t - 2*a) `mod` 26)
Avanti, vorrei usare nome e conquistare per scomporre una sottofunzione autonomamente utilizzabile:
encode_characters k t = chr $ encode (ord k) (ord t)
encode x y = (x + y - 2*a) `mod` 26 + a
Ho anche dato un nome alla prima espressione per renderla più chiara e riutilizzabile. encode_characters
ora è facile da fare gratis-point con la tecnica da @Nefrubyr:
encode_characters = chr . encode `on` ord
Per quanto riguarda la seconda espressione, non posso produrre una forma che è più leggibile rispetto a qualsiasi mostrato nelle altre risposte e sono tutti meno leggibile rispetto alla forma punto-saggio. Vorrei quindi suggerire di fermarsi refactoring a questo punto e ammirare la pulizia e la riusabilità del codice risultante.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~
PS: come un esercizio, a seconda del contesto del problema, alcune leggere modifiche delle interfacce funzionali (quali dati in quale forma è passato attraverso i funzioni) può risultare più semplificazioni generalizzando problema
. A. Implementare e semplificare funzione encode_n_characters :: [Char] -> Char
dove encode_characters k t = encode_n_characters [k, t]
. E 'il risultato più semplice del funzione a due argomenti specializzato?
B. Implementare una funzione definita encode'
via encode' (x + y) = encode x y
e reimplementare encode_characters
utilizzando questa funzione. Fa una delle funzioni diventano più semplici? È l'implementazione più semplice complessiva? È encode'
più o meno riutilizzabili di encode
?
IRC, #haskell , e chiedere lambdabot :
<you> @pl (\k t -> chr $ a + flip mod 26 (ord k + ord t -2*a))
<lambdabot> [the answer]