Conseils pour jouer au golf à Clean

17

Quels conseils généraux avez-vous pour jouer au golf dans Clean? Veuillez ne publier que des idées qui peuvent être appliquées aux problèmes de golf de code en général et qui sont au moins quelque peu spécifiques à Clean.

Si vous n'avez jamais entendu parler de Clean, vous pouvez en savoir plus ici .
Ou, vous pouvez rejoindre la salle de chat .

Οurous
la source

Réponses:

10

A éviter import StdEnvsi possible

Pour accéder aux fonctions intégrées, même en apparence élémentaires comme (==)ou map, une instruction d'importation est nécessaire, généralement import StdEnvparce qu'elle importe les modules les plus courants comme StdInt, StdBoolet ainsi de suite (voir ici pour plus d'informations sur StdEnv).

Cependant, il peut être possible d'éviter cette importation pour certains défis et d'utiliser simplement les fonctionnalités du langage de base telles que les compréhensions de liste et la correspondance de modèles.

Par exemple, au lieu de

import StdEnv 
map f list

on peut écrire

[f x\\x<-list]

Liste des alternatives:

Certaines fonctions ou invocations de fonctions qui ont besoin import StdEnv, une alternative qui n'a pas besoin de l'importation et une estimation approximative des octets enregistrés.

  • hd-> (\[h:_]=h), ~ 6 octets
  • tl-> (\[_:t]=t), ~ 6 octets
  • map f list-> [f x\\x<-list], ~ 10 octets
  • filter p list-> [x\\x<-list|p x], ~ 11 octets
  • (&&)-> %a b|a=b=a;%, ~ 6 octets
  • (||)-> %a b|a=a=b;%, ~ 6 octets
  • not-> %a|a=False=True;%, ~ 1 octet
  • and-> %[a:r]|a= %r=a;%_=True, ~ 0 octet
  • or-> %[a:r]|a=a= %r;%_=False, ~ 0 octet

Il est peu probable que les derniers enregistrent réellement des octets, car un remplacement direct produit plus d'octets que l'importation, mais cela pourrait être possible dans les cas où la récursivité sur la liste est de toute façon nécessaire.

Cette astuce a été utilisée avec succès ici .

Laikoni
la source
N'est-ce pas import StdEnv+ a and b(21 octets) plus petit que %[a:r]|a= %r=a;%_=True(22 octets), cependant? Ou serait-ce import StdEnv+ a=True and b=True(31 octets), auquel cas il est en effet nettement plus court? (Je n'ai jamais programmé en Clean, en passant.)
Kevin Cruijssen
@KevinCruijssen Nous venions de discuter de cela dans le chat . Il est vrai que ceux-ci sont peu susceptibles d'économiser des octets, sauf peut-être lorsque le programme doit de toute façon récurrencer une liste.
Laikoni
4
Ah ok. Il serait peut-être également utile d'indiquer le nombre d'octets enregistrés avec l'alternative (c'est-à-dire map f list -> [f x\\x<-list] (11 bytes saved)(ou quelque chose de similaire)).
Kevin Cruijssen
@KevinCruijssen Done.
Laikoni
5

Savoir apprendre la langue

Après tout, comment peut-on jouer au golf dans une langue qu'il ne peut pas utiliser!

En ligne

Clean n'est pas un langage bien connu ou bien documenté, et le nom ne permet certainement pas de trouver facilement les ressources nécessaires pour remédier à ces problèmes ... ou le fait-il?

Clean était à l'origine appelé Concurrent Clean , qui est toujours utilisé dans la préface de presque tous les documents liés à Clean - donc si vous recherchez Clean, recherchez plutôt Concurrent Clean.

L'une des similitudes les plus remarquables de Clean avec Haskell (dont il y en a beaucoup) est l'existence de Cloogle , qui est un moteur de recherche de fonctions couvrant les bibliothèques avec lesquelles Clean est livré.

Localement

Les bibliothèques avec lesquelles Clean est fourni se présentent sous la forme de fichiers source Clean assez documentés et décemment commentés, qui peuvent être parcourus à l'aide de l'EDI.
(Il est également livré avec des exemples de programmes complets, ci-dessous $INSTALL/Examples.)

En parlant de cela, la version Windows de Clean est livrée avec un IDE - bien qu'elle soit assez limitée par les normes modernes, c'est bien mieux que d'utiliser un éditeur de texte et la ligne de commande.
Les deux fonctionnalités les plus utiles (dans le cadre de l'apprentissage) sont:

  • Vous pouvez double-cliquer sur une erreur pour voir sur quelle ligne elle se trouve
  • Vous pouvez mettre en surbrillance un nom de module et appuyer sur [Ctrl]+[D]pour ouvrir le fichier de définition (ou l'utiliser [Ctrl]+[I]pour le fichier d'implémentation), et basculer entre le fichier de définition et d'implémentation avec[Ctrl]+[/]
Οurous
la source
4

Oubliez l'encodage des caractères

Le compilateur de Clean ne se soucie pas de l'encodage que vous pensez avoir enregistré le fichier source, juste des valeurs d'octets dans le fichier. Cela a des conséquences intéressantes.

Dans le corps du code source, seuls les octets avec des points de code correspondant aux caractères ASCII imprimables sont autorisés, en plus de ceux de \t\r\n.

Littéraux:

Dans Stringet [Char]littéraux ( "stuff"et ['stuff']respectivement), tous les octets à l'exception de 0 sont autorisés, avec la mise en garde que "et 'doivent être échappés (pour Stringet [Char]respectivement), et que les retours à la ligne et les retours carraige doivent être remplacés par \net \r(également respectivement).

En Charlittéraux, tout octet sauf 0 est autorisé, ce qui signifie que:

'\n'

'
'

Sont les mêmes, mais le second est un octet plus court.

Échapper:

À part les lettres d'échappement standard \t\r\n(etc.), toutes les séquences d'échappement non numériques de Clean sont destinées à la barre oblique ou à la citation utilisée pour délimiter le littéral dans lequel l'échappement se trouve.

Pour les séquences d'échappement numériques, le nombre est traité comme une valeur octale terminée après trois chiffres. Cela signifie que si vous voulez un null suivi du caractère 1dans a String, vous devez utiliser "\0001"(ou "\0\61") et non "\01" . Cependant, si vous suivez l'évasion avec autre chose que des chiffres, vous pouvez omettre les zéros de tête.

Conséquences:

Cette bizarrerie avec la façon dont Clean gère ses fichiers sources permet Stringet ['Char']devient effectivement des séquences de nombres à 256 chiffres à la base - ce qui a une multitude d'utilisations pour le code-golf, comme le stockage d'index (jusqu'à 255, bien sûr).

Οurous
la source
3

Fonctions de nom avec symboles

Lors de la définition d'une fonction, il est souvent plus court d'utiliser une combinaison de !@$%^&*~-+=<:|>.?/\que d'utiliser des caractères alphanumériques, car cela vous permet d'omettre les espaces blancs entre les identifiants.

Par exemple: ?a=a^2est plus court que f a=a^2, et l'invoquer est également plus court.

Cependant :

Si l'identifiant de fonction est utilisé à côté d' autres symboles, qui peuvent se combiner pour former un identifiant différent mais valide , ils seront tous analysés comme un identifiant et vous verrez une erreur.

Par exemple: ?a+?banalyse comme? a +? b

Aditionellement:

Il est possible d'écraser les identifiants importés dans Clean, et donc les seuls identifiants de symboles à un seul caractère qui ne sont pas déjà utilisés dans le StdEnvsont @$?. L'écrasement ^-+(etc.) peut être utile si vous avez besoin de plus d'identifiants symboliques, mais attention à ne pas écraser celui que vous utilisez.

Οurous
la source
3

Connaissez vos nœuds K

Certaines des constructions les plus solides (pour le golf) dans les langages fonctionnels sont let ... in ....
Propre bien sûr, a cela, et quelque chose de mieux - le #.

Qu'est-ce qu'un nœud?

Les nettoyages #et l'omniprésence |(pattern guard) sont tous deux connus comme des «expressions de nœuds».
Notamment, ils vous permettent de programmer imperatively- ish en propre ( ce qui est vraiment bon ici!).

Le #(let-before):

Ces deux calculent la valeur d'un entier donné sous forme de chaîne, multiplié par la somme de ses caractères

f s=let i=toInt s;n=sum[toInt c\\c<-:s]in n*i

f s#i=toInt s
#s=sum[toInt c\\c<-:s]
=s*i

Notez comment la version avec #est plus courte et comment nous pouvons redéfinir s. Ceci est utile si nous n'avons pas besoin de la valeur d'une variable lorsque nous la recevons, nous pouvons donc simplement réutiliser le nom. ( letpeut rencontrer des problèmes lorsque vous faites cela)

Mais l'utilisation letest plus facile lorsque vous avez besoin de quelque chose commeflip f = let g x y = f y x in g

Le |(modèle de garde):

Le modèle Pattern Guard peut être utilisé comme ceux de nombreux autres langages fonctionnels - mais il peut également être utilisé comme un impératif if ... else .... Et une version plus courte de l'expression ternaire.

Par exemple, ceux-ci renvoient tous le signe d'un entier:

s n|n<>0|n>0=1= -1
=0

s n=if(n<>0)if(n>0)1(-1)0

s n|n>0=1|n<0= -1=0

Bien sûr, la dernière qui utilise la garde plus traditionnellement est la plus courte, mais la première montre que vous pouvez les imbriquer (mais seules deux clauses de retour inconditionnelles peuvent apparaître sur la même ligne dans la règle de disposition), et la seconde montre ce que le le premier le fait logiquement.

Une note:

Vous pouvez utiliser ces expressions pratiquement n'importe où. En lambdas, case ... of, let ... in, etc.

Οurous
la source
1

Si vous utilisez un, Stringvous devriez utiliserText

La conversion en cordes et la manipulation de cordes (le {#Char}/ Stringkind, pas le [Char]kind) est assez longue et mauvaise pour le golf. Le Textmodule y remédie.

Conversion:

Textdéfinit l'opérateur <+pour deux types quelconques qui ont été toStringdéfinis.
Cet opérateur, utilisé comme a<+best le même que toString a+++toString b- économisant au moins 19 octets . Même si vous incluez l'importation supplémentaire ,Textet que vous ne l'utilisez qu'une seule fois, elle économise toujours 14 octets!

Manipulation:

Textdéfinit quelques agrafes de manipulation de chaînes manquantes dans StdEnv:

  • L'opérateur +pour les chaînes, qui est beaucoup plus court que +++(de StdEnv)
  • indexOf, avec le comportement de type C de retour -1au lieu d' Nothingéchec
  • concat, qui concatène une liste de chaînes
  • join, qui joint une liste de chaînes à l'aide d'une chaîne de séparation
  • split, qui divise une chaîne en une liste de chaînes sur une sous-chaîne
Οurous
la source
1

Utilisez des lambdas plus courts

Parfois, vous vous retrouvez à utiliser une expression lambda (pour passer à map, ou sortBy, etc.). Lorsque vous faites cela (en écrivant des lambdas), il y a plusieurs façons de le faire.

Le droit chemin:

C'est sortBy, avec une liste de tri lambda idiomatique du plus long au plus court

sortBy (\a b = length a > length b)

L'autre bonne façon:

Si vous utilisez Data.Func, vous pouvez également faire

sortBy (on (>) length)

Le chemin court:

C'est la même chose, mais avec une syntaxe golfeur

sortBy(\a b=length a>length b)

L'autre côté:

L'utilisation de la composition n'est pas plus courte cette fois, mais elle peut parfois être plus courte

sortBy(\a=(>)(length a)o length)

L'autre autre manière:

Bien que ce soit un peu artificiel ici, vous pouvez utiliser des gardes dans les lambdas

sortBy(\a b|length a>length b=True=False)

Et aussi des expressions de noeud avant

sortBy(\a b#l=length
=l a>l b)

Une note:

Il existe deux autres formes de lambda, (\a b . ...)et (\a b -> ...), la dernière est identique à la =variante, et la première existe pour une raison quelconque et semble souvent que vous essayez d'accéder à une propriété de quelque chose au lieu de définir une lambda, alors ne le faites pas ne l'utilise pas.

Οurous
la source
1
Après avoir vu certains de vos programmes de golf, j'avais l'impression que la syntaxe lambda habituelle de Clean \a=... était : P
Ørjan Johansen
Vous pouvez également ajouter les gardes dans lambda, comme utilisé ici . Ceci n'est pas documenté (cela contredit même le rapport linguistique), mais fonctionne. Aussi, ->et =pour lambdas sont identiques en ce qui concerne le compilateur ( ->est l'ancienne syntaxe). Seul .est différent (mais je ne sais pas exactement comment).
Et dans cet exemple particulier, vous pourriez envisager d'utiliser on(<)length, même si l' Data.Funcimportation vous interrompra, sauf si vous en avez déjà besoin.
@Keelan Cool. Je le mettrai à jour plus tard dans la journée. Je pense que vous pouvez également utiliser let-before ( #) dans les lambdas.
18urous
Oui, vous pouvez :-)
0

Utiliser des littéraux de liste de caractères

Un littéral de liste de caractères est une façon abrégée d'écrire quelque chose comme ['h','e','l','l','o']as ['hello'].

Ce n'est pas la limite de la notation, par exemple:

  • repeat'c'devient ['c','c'..]devient['cc'..]
  • ['z','y'..'a'] devient ['zy'..'a']
  • ['beginning']++[a,b,c]++['end'] devient ['beginning',a,b,c,'end']
  • ['prefix']++suffix devient ['prefix':suffix]

Ils fonctionnent également en correspondance:

  • ['I don't care about the next character',_,'but I do care about these ones!']
Οurous
la source
0

Parfois codeplus court

Clean a un tas de fonctions vraiment utiles dans les bibliothèques standard, dont certaines sont incroyablement verbeuses à utiliser sans accès *Worldet à l'aide*World dans le code-golf est généralement une mauvaise idée de toute façon.

Pour contourner ce problème, ccallvous pouvez souvent utiliser des codeblocs internes à la place.

Quelques exemples:

Le temps du système

import System.Time,System._Unsafe
t=toInt(accUnsafe(time))

Ce qui précède est de 58 octets, mais vous pouvez économiser 17 octets (jusqu'à 40 + 1) avec:

t::!Int->Int
t _=code{ccall time "I:I"
}

Nombres aléatoires

Celui-ci ne sauvegarde pas les octets seul, mais évite d'avoir à faire circuler une liste de genRandInt

s::!Int->Int
s _=code{ccall time "I:I"ccall srand "I:I"
}
r::!Int->Int
r _=code{ccall rand "I:I"
}

Autres utilisations

En plus de ces deux, qui sont probablement les principales utilisations de ceci dans code-golf, vous pouvez appeler n'importe quelle fonction nommée (y compris, mais sans s'y limiter, chaque syscall), incorporer un assemblage arbitraire avec instruction <byte>et incorporer du code pour la machine ABC.

Οurous
la source