2010-03-17 19 views
17

J'ai ce code que je veux faire sans point;Point-libre dans Haskell

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

Comment puis-je faire cela?

Existe-t-il également des règles générales pour le style sans point autres que "penser à ceci et trouver quelque chose"?

+3

Pourquoi voudriez-vous rendre ce point-gratuit? – yfeldblum

+1

Parce que pouvoir écrire du code sans point ressemble à l'une des propriétés d'un bon programmeur Haskell. – Igor

+10

Parfois, le code sans point est plus clair que son alternative non-ponctuelle, et c'est une bonne idée d'utiliser un style sans point. Ce n'est pas un de ces temps. – dave4420

Répondre

52

Pour activer une fonction

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

en forme de point libre, j'essaie généralement de suivre ce qui se fait au dernier paramètre z et écrire la fonction comme

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

Ensuite, je peux annuler sur les z s pour obtenir

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

Puis répéter g le processus pour y et x devrait se terminer par func sous forme sans point.Une transformation essentielle pour reconnaître dans ce processus est le suivant:

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

Il est également important de se rappeler que l'évaluation partielle, vous pouvez « casser » le dernier argument à une fonction:

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

Pour votre particulier fonction, tenez compte des flux qui k et t passent par:

  1. Appliquer ord à chacun d'eux
  2. Ajouter les résultats
  3. Soustraire 2 * a
  4. Prenez le résultat mod 26
  5. Ajouter un
  6. Appliquer chr

donc comme une première tentative de simplification, nous obtenons:

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

Notez que vous pouvez éviter flip en utilisant une section sur mod, et les sections utilisant - sont désordonnées dans Haskell, donc il y a une fonction subtract (elles se heurtent à la syntaxe pour écrire des nombres négatifs: (-2) signifie négatif 2, et n'est pas la même chose que subtract 2). Dans cette fonction, ord k + ord t est un excellent candidat pour utiliser Data.Function.on (link). Ce combinateur utile nous permet de remplacer ord k + ord t avec une fonction appliquée à k et t:

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

Nous sommes maintenant très près d'avoir

func k t = (function pipeline) k t 

et donc

func = (function pipeline) 

Malheureusement Haskell est un peu brouillon quand il s'agit de composer une fonction binaire avec une séquence de fonctions unaires, mais il y a un truc (je verrai si je peux trouver un bonne référence pour elle), et nous nous retrouvons avec:

import Data.Function (on) 

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

qui est presque une belle conduite de fonction de point sans propre, sauf pour ce truc de composition laid. En définissant l'opérateur .: suggéré dans les commentaires on this page, ce tidies un peu à:

import Data.Function (on) 

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

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

Pour polir ce un peu plus, vous pouvez ajouter des fonctions d'aide à séparer la lettre < -> conversion Int de la Caesar cipher arithmétique . Par exemple: letterToInt = subtract a . ord

+3

+1 C'est en fait assez lisible – jberryman

+3

Incroyable combinateur fu . +1 –

+22

+1 pour utiliser le combinateur de seins – SamB

0

Connect sur IRC, #haskell et ask lambdabot !:

<you> @pl (\k t -> chr $ a + flip mod 26 (ord k + ord t -2*a)) 
<lambdabot> [the answer] 
+0

+1 pour de bonnes références. -1 pour ne pas afficher de réponse réelle. – Earlz

+0

Pas encore publié parce que j'essaie de comprendre à la main, au lieu de demander lambdabot ... Une modification est à venir. –

+0

Bah, je suis venu avec let F1 = \ a -> (chr.). ((a +).). ((flip mod 26).). (. ord). (+). ((-) (2 * a)). ord - mais cela donne de mauvais résultats et je n'ai pas envie de le déboguer maintenant :) –

10

aussi sont là quelques règles générales pour le style libre de point autre que "penser à ce amd trouver quelque chose"?

Vous pouvez toujours tricher et utiliser l'outil « pl » de lambdabot (soit en allant #haskell sur freenode ou en utilisant par exemple ghci on acid). Pour votre code pl donne:

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

Ce qui est pas vraiment une amélioration si vous me demandez.

+7

C'est pourquoi "pl" est court pour inutile, pas point-free :) –

3

Il existe certainement un ensemble d'astuces pour transformer une expression en style sans point. Je ne prétends pas être un expert, mais voici quelques conseils.

Premièrement, vous voulez isoler les arguments de la fonction dans le terme le plus à droite de l'expression.Vos principaux outils seront mis à flip et $, en utilisant les règles:

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

f et g sont des fonctions et a et b sont des expressions. Donc, pour commencer:

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

Maintenant, nous avons besoin de t sur le côté droit. Pour ce faire, utilisez la règle:

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

Et:

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

Maintenant, nous devons tourner tout à gauche de k et t dans un grand terme de fonction, de sorte que nous avons un expression de la forme (\k t -> f k t). C'est là que les choses deviennent un peu hallucinantes. Pour commencer, notez que tous les termes jusqu'à la dernière $ sont des fonctions avec un seul argument, afin que nous puissions les composer:

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

Maintenant, nous avons une fonction de type Char -> Char -> Int que nous voulons composer avec un fonction de type Int -> Char, ce qui donne une fonction de type Char -> Char -> Char. Nous pouvons y parvenir en utilisant la (très bizarre) règle

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

qui nous donne:

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

Maintenant, nous pouvons simplement appliquer une réduction de la bêta:

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

L'utilisation des instances '->' de Monad, Applicative ou Arrow sont également des astuces. – ephemient

+0

'f (g a) ==> f $ g a' n'aide pas vraiment. Le côté droit est toujours 'f $ (g a)'. Ce que vous voulez, c'est la composition de la fonction. 'f (g a)' est '(f. g) a'. – Prateek

3

Je suppose que le but de votre remise de points est de rendre le code plus concis et plus lisible. Je pense donc qu'il est sage de faire aussi d'autres refactorings vers la simplification qui pourrait alors faciliter la suppression des variables.

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

D'abord, le flip est inutile:

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

Ensuite, j'utiliser nom et conquérir à factoriser une sous-fonction indépendante utilisable:

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

J'ai aussi a donné un nom à la première expression pour le rendre plus clair et réutilisable. encode_characters est maintenant facile à faire sans point en utilisant la technique de @Nefrubyr:

encode_characters = chr . encode `on` ord 

Quant à la seconde expression, je ne peux pas produire une forme qui est plus lisible que tout montré dans les autres réponses et ils sont tous moins lisible que la forme point par point. Je suggère donc d'arrêter de refactoriser à ce stade et d'admirer la propreté et la réutilisabilité du code qui en résulte.

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

PS: comme un exercice, en fonction du contexte du problème, une légère modification des interfaces de fonction (quelles données sous quelle forme est passée dans les fonctions) pourrait donner plus de simplifications en généralisant le problème.

A. Mettre en œuvre et simplifier la fonction encode_n_characters :: [Char] -> Charencode_characters k t = encode_n_characters [k, t]. Le résultat est-il plus simple que la fonction spécialisée à deux arguments?

B. Implémentez une fonction encode' définie via encode' (x + y) = encode x y et réimplémentez encode_characters en utilisant cette fonction. Est-ce que l'une ou l'autre des fonctions devient plus simple? La mise en œuvre est-elle plus simple dans l'ensemble? Est-ce que encode' est plus ou moins réutilisable que encode?