Quels conseils généraux avez-vous pour jouer au golf à Haskell? Je cherche des idées qui puissent être appliquées aux problèmes de code de golf en général et qui sont au moins quelque peu spécifiques à Haskell. Merci de ne publier qu'un pourboire par réponse.
Si vous débutez dans le golf à Haskell, veuillez consulter le Guide des règles de golf à Haskell . Il existe également une salle de discussion dédiée à Haskell: Of Monads and Men .
Réponses:
Définir des opérateurs infixes au lieu de fonctions binaires
Cela économise généralement un ou deux espaces par définition ou appel.
contre.
Les symboles disponibles pour les opérateurs de 1 octet sont
!
,#
,%
,&
et?
. Toute autre ponctuation ASCII est déjà définie en tant qu'opérateur par Prelude (tel que$
) ou a une signification spéciale dans la syntaxe de Haskell (telle que@
).Si vous avez besoin de plus de cinq opérateurs, vous pouvez utiliser des combinaisons de ce qui précède, telles que
!#
, ou certains caractères de ponctuation Unicode, tels que ceux-ci (tous les 2 octets en UTF-8):la source
(x!y)z=x+y*z
et les(x#y)z u=x*z+y*u
deux fonctionnent comme prévu.\f g(!)x y->f g!x y
place de\f g j x y->j(f g)(x y)
g x=…;g(f x)
est plus long que_?x=…;0!f x
Utilisez des notations inutiles (ou libres) le cas échéant
Souvent, une fonction avec un ou deux paramètres peut être écrite sans point.
Ainsi, une recherche d'une liste de n-uplets dont les éléments sont échangés s'écrit naïvement comme suit:
(le type est là juste pour vous aider à comprendre ce qu'il fait.)
pour nos besoins c'est beaucoup mieux:
la source
Utilisez la liste monade
Un examen rapide:
Exemples:
Répéter une liste deux fois
Plus court
concatMap
concat
Compréhension plus courte + listeproduit cartésien
Liste de coordonnées sur un réseau
la source
[0..b]>>[a]
au lieu dereplicate a b
.a<$[1..b]
est encore plus court, pourreplicate
.=<<
forces pour importerControl.Monad
. Si vous n'en avez pas besoin pour une autre raison, échanger les arguments et utiliser>>=
semble plus concis.Data.Traversable
toute façon, l'exemple de produit cartésien peut être raccourcifor["Hh","io",".!"]id
.(=<<)
est en prélude , en fait! Je l'ai beaucoup utilisé.Utilisez des gardes pas conditionnels:
Utilisez des points-virgules et non des tirets
Utiliser des expressions booléennes pour les fonctions booléennes
(SO est une douleur de me laisser poster ces séparément)
la source
&&
dans une liste de compréhension.True
=>1>0
f a=if a>0 then 3 else 7
interact :: (String → String) → IO ()
Les gens oublient souvent que cette fonction existe - elle saisit tout le stdin et l'applique à une fonction (pure). Je vois souvent
main
-code le long des lignes detandis que
est un peu plus court. C'est dans le prélude donc pas besoin d'importations!
la source
Utilisez GHC 7.10
La première version de GHC contenant ces éléments a été publiée le 27 mars 2015 .
C'est la dernière version, et Prelude a de nouveaux ajouts utiles pour le golf:
Les
(<$>)
et(<*>)
opérateursCes opérateurs utiles de
Data.Applicative
made in!<$>
est justefmap
, donc vous pouvez remplacermap f x
etfmap f x
avecf<$>x
partout et récupérer des octets. Aussi,<*>
est utile dans l'Applicative
instance pour les listes:L'
(<$)
opérateurx<$a
est équivalent àfmap (const x) a
; c'est-à-dire remplacer chaque élément d'un conteneur parx
.C'est souvent une bonne alternative à
replicate
:4<$[1..n]
est plus courte quereplicate n 4
.La proposition pliable / transversale
Les fonctions suivantes ont été levées du travail sur les listes
[a]
auxFoldable
types générauxt a
:Cela signifie qu'ils travaillent maintenant également
Maybe a
, où ils se comportent exactement comme des "listes comportant au plus un élément". Par exemplenull Nothing == True
, ousum (Just 3) == 3
. De même,length
renvoie 0 pourNothing
et 1 pour lesJust
valeurs. Au lieu d'écrire,x==Just y
vous pouvez écrireelem y x
.Vous pouvez également les appliquer sur des tuples, ce qui fonctionne comme si vous aviez appelé en
\(a, b) -> [b]
premier. C'est presque complètement inutile, maisor :: (a, Bool) -> Bool
est un personnage plus court quesnd
, etelem b
est plus court que(==b).snd
.Le monoïde fonctionne
mempty
etmappend
Pas souvent un épargnant de vie, mais si vous pouvez en déduire le type,
mempty
un octet est plus court queNothing
, alors voilà.la source
<*>
transformer en prélude! Cela devrait être utile même quand ce n'est pas du code golf (applicatif est un mot tellement long).[1..2]
dedans. c'est juste[1,2]
<*
partirApplicative
, ce qui est pour les listesxs <* ys == concatMap (replicate (length ys)) xs
. Ceci est différent dexs >> ys
ouxs *> ys
qui estconcat (replicate (length ys)) xs
.pure
qui est un plus courtreturn
est venu à ce stade aussi.<>
au lieu demappend
, c’est maintenant (avec GHC 8.4.1) une partie dePrelude
.Utilisez
1<2
au lieu deTrue
et1>2
au lieu deFalse
.la source
f=max 10
.if(true)
dans d'autres langues. dans le prélude, sinon est en réalité la valeur booléenneTrue
.otherwise
.Utiliser les compréhensions de liste (de manière intelligente)
Tout le monde sait que leur syntaxe est utile, souvent plus courte que
map
+ un lambda:Ou
filter
(et éventuellementmap
en même temps):Mais il y a des utilisations plus étranges qui sont utiles de temps en temps. D'une part, une compréhension de liste n'a pas besoin de contenir de
<-
flèches du tout:Ce qui signifie qu'au lieu de
if p then[x]else[]
, vous pouvez écrire[x|p]
. De plus, pour compter le nombre d'éléments d'une liste satisfaisant une condition, vous écririez intuitivement:Mais c'est plus court:
la source
Connaître ton
Prelude
Lancez GHCi et parcourez la documentation Prelude . Chaque fois que vous croisez une fonction dont le nom est court, il peut être utile de rechercher des cas où cela pourrait être utile.
Par exemple, supposons que vous souhaitiez transformer une chaîne
s = "abc\ndef\nghi"
en chaînes séparées par des espaces"abc def ghi"
. La manière évidente est:Mais vous pouvez faire mieux si vous abusez
max
et le fait que\n < space < printable ASCII
:Un autre exemple est
lex :: String -> [(String, String)]
, qui fait quelque chose d'assez mystérieux:Essayez
fst=<<lex s
d’obtenir le premier jeton d’une chaîne en évitant les espaces blancs. Voici une solution intelligente par henkma qui utiliselex.show
sur desRational
valeurs.la source
Correspondre à une valeur constante
Une compréhension de liste peut correspondre à un motif sur une constante.
Ceci extrait les 0 d’une liste
l
, c’est-à-dire crée une liste contenant autant de 0 qu’il y en al
.Cela fait une liste de autant
1
de éléments qu'il y a d'élémentsl
quif
prend la liste vide (en utilisant<$>
infixmap
). Appliquezsum
pour compter ces éléments.Comparer:
Une constante peut être utilisée dans le cadre d'une correspondance de modèle. Ceci extrait les secondes entrées de tous les tuples dont la première est
0
.Notez que tous ces éléments nécessitent un littéral constant réel et non la valeur d'une variable. Par exemple,
let x=1 in [1|x<-[1,2,3]]
affichera[1,1,1]
, pas[1]
parce que lax
liaison externe est écrasée.la source
Utilisez
words
au lieu d'une longue liste de chaînes. Ce n'est pas vraiment spécifique à Haskell, d'autres langues ont aussi des astuces similaires.la source
Connaissez vos fonctions monadiques
1)
simuler des fonctions monadiques en utilisant
mapM
.beaucoup de fois le code aura
sequence(map f xs)
, mais il peut être remplacé parmapM f xs
. même si vous utilisez seulementsequence
seul, il est plus longmapM id
.2)
combiner des fonctions en utilisant
(>>=)
(ou(=<<)
)la fonction monad version de
(>>=)
est définie comme suit:cela peut être utile pour créer des fonctions qui ne peuvent pas être exprimées sous forme de pipeline. par exemple,
\x->x==nub x
est plus long quenub>>=(==)
, et\t->zip(tail t)t
est plus long quetail>>=zip
.la source
Applicative
et nonMonad
de la mise en œuvre,pure
elle est plus courteconst
et m'a effectivement aidée auparavant.Les arguments peuvent être plus courts que les définitions
Je viens de me outgolfed d'une manière très curieuse par henkma .
Si une fonction auxiliaire
f
de votre réponse utilise un opérateur qui n'est pas utilisé ailleurs dans votre réponse etf
est appelée une fois, définissez-le comme argumentf
.Cette:
Est-ce que deux octets sont plus longs que cela:
la source
Utilisez le contre opérateur (:)
lors de la concaténation de listes, si la première est de longueur 1, utilisez
:
plutôt.la source
1:2:3:x
plutôt que[1,2,3]++x
.Ne pas utiliser les backticks trop souvent. Les backticks sont un outil intéressant pour créer des sections de fonctions de préfixe, mais ils peuvent parfois être mal utilisés.
Une fois que j'ai vu quelqu'un écrire cette sous-expression:
Bien que ce soit la même chose que juste
v x
.Un autre exemple est l'écriture
(x+1)`div`y
par opposition àdiv(x+1)y
.Je le vois souvent
div
etelem
plus souvent parce que ces fonctions sont généralement utilisées comme infixes dans le code normal.la source
Utiliser des gardes de modèle
Ils sont plus courts qu'un
let
ou un lambda qui déconstruit les arguments d'une fonction que vous définissez. Cela aide lorsque vous avez besoin de quelque chose commefromJust
deData.Maybe
:est plus long que
est plus long que
est plus long que
En fait, ils sont plus courts même lorsqu'ils lient une ancienne valeur ordinaire au lieu de la déconstruire: voir le conseil de xnor .
la source
e
n'est pas réellement un jeton mais une expression plus longue qui doit$
être précédée, ce qui est généralement le cas.Conditionnel plus court
est équivalent à
Voilà comment cela fonctionne:
la source
if b then y else x
?bool
serait pas plus courte, car vous n'avez pas besoin d'une liste de compréhensionTravailler avec le signe moins
Le signe moins
-
est une exception ennuyeuse à de nombreuses règles de syntaxe. Cette astuce répertorie quelques manières d’exprimer la négation et la soustraction en Haskell. S'il vous plaît laissez-moi savoir si j'ai manqué quelque chose.Négation
e
, il suffit de faire-e
. Par exemple,-length[1,2]
donne-2
.e
est même moyennement complexe, vous aurez besoin de parenthèsese
, mais vous pouvez généralement enregistrer un octet en les déplaçant:-length(take 3 x)
est plus court que-(length$take 3 x)
.e
est précédé de=
ou par un opérateur infixe de fixité inférieur à 6, vous avez besoin d'un espace:f= -2
définitf
etk< -2
teste sik
est inférieur à-2
. Si la fixité est de 6 ou plus, vous avez besoin de parens:2^^(-2)
donne0.25
. Vous pouvez généralement réorganiser les éléments pour vous en débarrasser: par exemple, faites à la-k>2
place dek< -2
.!
est un opérateur, alors-a!b
est analysé comme(-a)!b
si la fixité de!
était au plus 6 (-1<1
donne doncTrue
), et-(a!b)
sinon (-[1,2]!!0
donnait ainsi-1
). La fixité par défaut des opérateurs définis par l'utilisateur et des fonctions associées à un point arrière est de 9; ils respectent donc la deuxième règle.map
etc.), utilisez la section(0-)
.Soustraction
k
, utilisez la section(-k+)
qui ajoute-k
.k
peut même être une expression assez complexe:(-2*length x+)
fonctionne comme prévu.pred
plutôt, à moins que cela ne nécessite un espace des deux côtés. Cela est rare et se produit généralement avecuntil
une fonction définie par l'utilisateur, car ellemap pred x
peut être remplacée parpred<$>x
etiterate pred x
par[x,x-1..]
. Et si vous avezf pred x
quelque part, vous devriez probablement définirf
comme une fonction infixe de toute façon. Voir cette astuce .la source
Essayez de réorganiser les définitions de fonctions et / ou les arguments
Vous pouvez parfois enregistrer quelques octets en modifiant l'ordre des cas de correspondance de modèle dans une définition de fonction. Ces économies sont bon marché, mais faciles à négliger.
Par exemple, considérons la version antérieure suivante de (une partie de) cette réponse :
C'est une définition récursive de
?
, le cas de base étant la liste vide. Comme ce[]
n'est pas une valeur utile, nous devrions échanger les définitions et les remplacer par le caractère générique_
ou un argument facticey
, en sauvegardant un octet:De la même réponse, considérons cette définition:
La liste vide apparaît dans la valeur de retour, nous pouvons donc économiser deux octets en échangeant les cas:
En outre, l'ordre des arguments de fonction peut parfois faire une différence en vous permettant de supprimer les espaces inutiles. Considérons une version antérieure de cette réponse :
Il y a un espace blanc ennuyeux entre
h
etp
dans la première branche. On peut s'en débarrasser en définissant à lah a p q
place deh p q a
:la source
Ne perdez pas le garde "autrement"
Un garde final qui est un fourre-tout
True
(plus court que1>0
) peut être utilisé pour lier une variable. Comparer:Comme la garde est obligatoire et qu'elle est autrement perdue, il en faut peu pour que cela en vaille la peine. Il suffit d'enregistrer une paire de parens ou de lier une expression de longueur 3 utilisée deux fois. Parfois, vous pouvez nier les gardes pour que le cas final soit l'expression qui utilise le mieux la liaison.
la source
Utilisez
,
au lieu de&&
dans les gardesPlusieurs conditions dans un garde qui doivent toutes tenir peuvent être combinés avec au
,
lieu de&&
.la source
f xs m | [x] <- xs, Just y <- m, x > 3 = y
Syntaxe plus courte pour les déclarations locales
Parfois, il est nécessaire de définir une fonction ou un opérateur local, mais écrire
where
oulet…in
relever le niveau supérieur en ajoutant des arguments supplémentaires coûte cher .Heureusement, Haskell utilise une syntaxe déroutante et rarement utilisée mais raisonnablement succincte pour les déclarations locales :
Dans ce cas:
Vous pouvez utiliser cette syntaxe avec des déclarations multi-déclarations ou des déclarations multiples, et cela même s'imbrique:
Cela fonctionne également pour les variables de liaison ou d'autres modèles, bien que les gardes de modèle aient tendance à être plus courts pour cela, sauf si vous utilisez également des fonctions de liaison.
la source
[f 1|let f x=x+1]
.Éviter
repeat n
Chacune de ces quatre expressions produira une liste infinie de
n
's.C'est un conseil très spécifique, mais vous pouvez économiser jusqu'à 3 octets!
la source
n
est global, al=n:l;l
la même longueur et fonctionne pour (certaines) expressions plus longues. (Peut-être besoin d'espaces cependant.)Conditions plus courtes lorsqu'un résultat est la liste vide
Lorsque vous avez besoin d'un conditionnel qui renvoie la liste
A
ou la liste vide en[]
fonction de certaines conditionsC
, il existe des alternatives plus courtes aux constructions conditionnelles habituelles:Exemples: 1 , 2
la source
A
et[]
changé.*>
a plus de fixité que>>
(toujours un peu faible pour le confort.)Règles d'analyse Lambda
Une expression lambda n’a pas vraiment besoin de parenthèses; elle saisit tout plutôt goulûment pour que tout continue à être analysé, par exemple jusqu’à ce que
(foo$ \x -> succ x)
let a = \x -> succ x in a 4
main = getContents>>= \x -> head $ words x
est rencontré, et il y a quelques cas de bord étranges où cela peut vous sauver un octet ou deux. Je pense que cela
\
peut également être utilisé pour définir des opérateurs, donc lors de l’exploitation, vous aurez besoin d’un espace pour écrire un lambda directement après un opérateur (comme dans le troisième exemple).Voici un exemple où lambda était la chose la plus courte que je pouvais comprendre. Le code ressemble en gros à ceci:
la source
Remplacer
let
par lambdaCela peut généralement raccourcir une définition auxiliaire unique qui ne peut pas être liée à une garde ou définie globalement pour une raison quelconque. Par exemple, remplacez
par les 3 octets plus court
Pour plusieurs définitions auxiliaires, le gain est probablement plus petit, en fonction du nombre de définitions.
Si certaines définitions se réfèrent aux autres, il est encore plus difficile de sauvegarder des octets de cette façon:
L'avertissement principal avec ceci est que
let
vous permet de définir des variables polymorphes, mais pas les lambdas, comme l'a noté @ChristianSievers. Par exemple,résultats en
(1,1)
, maisdonne une erreur de type.
la source
let
, alors nous pouvons le fairelet f=id in (f 0,f True)
. Si nous essayons de réécrire cela avec lambda, il ne tapez pas check.Lier en utilisant des gardes
Lorsque vous définissez une fonction nommée, vous pouvez lier une expression à une variable dans une garde. Par exemple,
fait la même chose que
Utilisez cette option pour enregistrer les expressions répétées. Lorsque l'expression est utilisée deux fois, elle dépasse même la longueur 6, bien que des problèmes d'espacement et de priorité puissent changer cela.
(Dans l'exemple, si la variable d'origine
s
n'est pas utilisée, il est plus courtmais ce n'est pas vrai pour la liaison d'expressions plus complexes.)
la source
Just
exemple m'a fait penser que c'était pour la correspondance de motif extraire d'un conteneur, plutôt que de stocker sur une expression.Utiliser
(0<$)
au lieu delength
pour les comparaisonsLorsqu’on vérifie si une liste
a
est plus longue qu’une listeb
, on écrit généralementCependant, remplacer chaque élément des deux listes par la même valeur, par exemple
0
, puis comparer ces deux listes peut être plus court:Essayez-le en ligne!
La parenthèse est nécessaire du fait
<$
et les opérateurs de comparaison (==
,>
,<=
, ...) ont le même niveau de priorité 4, bien que dans certains autres cas , ils pourraient ne pas être nécessaire, sauver octets encore plus.la source
Plus court
transpose
Pour utiliser la
transpose
fonctionData.List
doit être importé. S'il s'agit de la seule fonction nécessitant une importation, vous pouvez enregistrer un octet en utilisant lafoldr
définition suivante detranspose
:Notez que le comportement n'est identique que pour une liste de listes de même longueur.
Je l'ai utilisé avec succès ici .
la source
Obtenir des suffixes
Utilisez
scanr(:)[]
pour obtenir les suffixes d'une liste:C'est beaucoup plus court
tails
qu'aprèsimport Data.List
. Vous pouvez faire des préfixes avecscanr(\_->init)=<<id
(trouvé par Ørjan Johansen).Cela économise un octet sur
la source
scanl(flip(:))[] "abc"
=["","a","ba","cba"]
mérite également d'être mentionné - parfois, les préfixes inversés importent peu.scanr(\_->init)=<<id