¿Qué consejos generales tienes para jugar al golf en Haskell? Estoy buscando ideas que puedan aplicarse a los problemas de código de golf en general que sean al menos algo específicos de Haskell. Por favor, publique solo un consejo por respuesta.
Si eres nuevo en el golf en Haskell, echa un vistazo a la Guía de reglas de golf en Haskell . También hay una sala de chat dedicada a Haskell: Of Monads and Men .

Respuestas:
Definir operadores infijos en lugar de funciones binarias.
Esto generalmente ahorra uno o dos espacios por definición o llamada.
vs.
Los símbolos disponibles para los operadores de 1 byte son
!,#,%,&, y?. Todos los demás signos de puntuación ASCII ya están definidos como operador por el Preludio (como$) o tienen un significado especial en la sintaxis de Haskell (como@).Si necesita más de cinco operadores, puede usar combinaciones de los anteriores, como
!#, o ciertos caracteres de puntuación Unicode, como estos (los 2 bytes en UTF-8):fuente
(x!y)z=x+y*zy(x#y)z u=x*z+y*uambos funcionan como se esperaba.\f g(!)x y->f g!x ylugar de\f g j x y->j(f g)(x y)g x=…;g(f x)es más largo que_?x=…;0!f xUse notación sin sentido (o libre) cuando sea apropiado
A menudo, una función con uno o dos parámetros puede escribirse sin puntos.
Entonces, una búsqueda de una lista de tuplas cuyos elementos se intercambian ingenuamente se escribe como:
(el tipo está ahí solo para ayudarlo a comprender lo que está haciendo).
para nuestros propósitos esto es mucho mejor:
fuente
Usa la lista mónada
Una revisión rápida:
Ejemplos:
Repetir una lista dos veces
Más corta
concatMapMenor
concat+ comprensión de la listaproducto cartesiano
Lista de coordenadas en una red
fuente
[0..b]>>[a]lugar dereplicate a b.a<$[1..b]es aún más corto, porreplicate.=<<te obliga a importarControl.Monad. Si no lo necesita por alguna otra razón, intercambiar los argumentos y usarlos>>=parece más conciso.Data.Traversabletodos modos, el ejemplo del producto cartesiano se puede acortarfor["Hh","io",".!"]id.(=<<)está en Preludio , en realidad! Lo he usado mucho.Use guardias no condicionales:
Use punto y coma, no sangrías
Usar expresiones booleanas para funciones booleanas
(SO es una molestia por dejarme publicar esto por separado)
fuente
&&cuando esté dentro de una lista de comprensión.True=>1>0f a=if a>0 then 3 else 7interact :: (String → String) → IO ()La gente a menudo olvida que esta función existe: toma todos los stdin y los aplica a una función (pura). A menudo veo
main-code a lo largo de las líneas demientras
es bastante más corto Está en el preludio, por lo que no es necesario importar.
fuente
Use GHC 7.10
La primera versión de GHC que contenía estas cosas fue lanzada el 27 de marzo de 2015 .
Es la última versión, y Prelude tiene algunas nuevas incorporaciones que son útiles para jugar al golf:
Los operadores
(<$>)y(<*>)¡Estos operadores útiles lo
Data.Applicativelograron!<$>es justofmap, para que pueda reemplazarmap f xyfmap f xconf<$>xtodas partes y recuperar bytes. Además,<*>es útil en laApplicativeinstancia para listas:El
(<$)operadorx<$aes equivalente afmap (const x) a; es decir, reemplazar cada elemento en un contenedor porx.Esta suele ser una buena alternativa a
replicate:4<$[1..n]es más corta quereplicate n 4.La propuesta plegable / transitable
Las siguientes funciones se eliminaron de trabajar en listas
[a]aFoldabletipos generalest a:Esto significa que ahora también trabajan
Maybe a, donde se comportan como "listas con un elemento como máximo". Por ejemplonull Nothing == True, osum (Just 3) == 3. Del mismo modo,lengthdevuelve 0 paraNothingy 1 para losJustvalores. En lugar de escribirx==Just ypuedes escribirelem y x.También puede aplicarlos en tuplas, que funciona como si hubiera llamado
\(a, b) -> [b]primero. Es casi completamente inútil, peroor :: (a, Bool) -> Booles un personaje más corto quesnd, yelem bes más corto que(==b).snd.Las funciones monoides
memptyymappendNo suele ser un salvavidas, pero si puede inferir el tipo,
memptyes un byte más corto queNothing, así que ahí está.fuente
<*>convertirlo en Preludio! Eso debería ser útil incluso cuando no es código golf (aplicativo es una palabra muy larga).[1..2]allí. eso es solo[1,2]<*deApplicativeque para las listas esxs <* ys == concatMap (replicate (length ys)) xs. Esto es diferentexs >> yso loxs *> ysque esconcat (replicate (length ys)) xs.pureque es más cortoreturnllegó en este punto también.<>lugar demappend, ahora es (con GHC 8.4.1) parte dePrelude.Use en
1<2lugar deTruey en1>2lugar deFalse.fuente
f=max 10.if(true)en otros idiomas. en el preludio, de lo contrario es en realidad el valor booleanoTrue.otherwise.Usar listas de comprensión (de manera inteligente)
Todos saben que son una sintaxis útil, a menudo más corta que
map+ una lambda:O
filter(y opcionalmentemapa al mismo tiempo):Pero hay algunos usos más extraños que son útiles de vez en cuando. Por un lado, una comprensión de la lista no necesita contener ninguna
<-flecha en absoluto:Lo que significa que en lugar de
if p then[x]else[], puedes escribir[x|p]. Además, para contar el número de elementos de una lista que satisfacen una condición, intuitivamente escribiría:Pero esto es más corto:
fuente
Conoce tu
PreludeEncienda GHCi y desplácese por la documentación de Prelude . Cada vez que cruza una función que tiene un nombre corto, puede ser útil buscar algunos casos en los que podría ser útil.
Por ejemplo, supongamos que se desea transformar una cadena
s = "abc\ndef\nghi"en una que es separada por espacios,"abc def ghi". La forma obvia es:Pero puede hacerlo mejor si abusa
maxy el hecho de que\n < space < printable ASCII:Otro ejemplo es
lex :: String -> [(String, String)], que hace algo bastante misterioso:Intenta
fst=<<lex sobtener el primer token de una cadena, saltando los espacios en blanco. Aquí hay una solución inteligente de henkma que utilizalex.showenRationalvalores.fuente
Hacer coincidir un valor constante
Una comprensión de la lista puede coincidir con un patrón en una constante.
Esto extrae los 0 de una lista
l, es decir, hace una lista de tantos 0 como hayl.Esto hace una lista de tantos
1como hay elementoslquefllevan a la lista vacía (usando<$>como infijomap). Aplicarsumpara contar estos elementos.Comparar:
Se puede usar una constante como parte de una coincidencia de patrón. Esto extrae las segundas entradas de todas las tuplas cuya primera entrada es
0.Tenga en cuenta que todos estos requieren un literal constante real, no el valor de una variable. Por ejemplo, se
let x=1 in [1|x<-[1,2,3]]generará[1,1,1], no[1], porque elxenlace externo se sobrescribe.fuente
Use en
wordslugar de una larga lista de cadenas. Esto no es realmente específico de Haskell, otros idiomas también tienen trucos similares.fuente
Conoce tus funciones monádicas
1)
simular funciones monádicas usando
mapM.muchas veces el código tendrá
sequence(map f xs), pero puede ser reemplazado pormapM f xs. incluso cuando solo se usasequencesolo es más largo entoncesmapM id.2)
combinar funciones usando
(>>=)(o(=<<))la versión de la función mónada
(>>=)se define así:Puede ser útil para crear funciones que no se pueden expresar como una canalización. por ejemplo,
\x->x==nub xes más largo quenub>>=(==), y\t->zip(tail t)tes más largo quetail>>=zip.fuente
Applicativey noMonadexiste la implementaciónpure, que es más cortaconsty realmente me ayudó antes.Los argumentos pueden ser más cortos que las definiciones.
Me acaban de outgolfed de una manera muy curiosa por henkma .
Si una función auxiliar
fen su respuesta usa un operador que no se usa en otra parte de su respuesta, yfse llama una vez, haga que el operador sea un argumento def.Esta:
Es dos bytes más largo que esto:
fuente
Use el operador contras (:)
al concatenar listas, si la primera es de longitud 1, úsela
:en su lugar.fuente
1:2:3:xlugar de[1,2,3]++x.No use backticks con demasiada frecuencia. Los backticks son una herramienta genial para crear secciones de funciones de prefijo, pero a veces se pueden usar de forma incorrecta.
Una vez que vi a alguien escribir esta subexpresión:
Aunque es lo mismo que justo
v x.Otro ejemplo es escribir
(x+1)`div`yen lugar dediv(x+1)y.Veo que ocurre alrededor
divy conelemmayor frecuencia porque estas funciones generalmente se usan como infijo en el código regular.fuente
Usar protectores de patrones
Son más cortas que una
leto una lambda que deconstruye los argumentos de una función que está definiendo. Esto ayuda cuando se necesita algo así comofromJustdesdeData.Maybe:es más largo que
es más largo que
es más largo que
De hecho, son más cortos incluso cuando se vincula un valor antiguo simple en lugar de deconstruir: vea la sugerencia de xnor .
fuente
erealidad no es un token, sino una expresión más larga que se necesita$antes, que suele ser el caso.Condicional más corto
es equivalente a
Así es como funciona:
fuente
if b then y else x?boolsería más corto ya que no necesita una lista de comprensiónTrabajando con el signo menos
El signo menos
-es una molesta excepción a muchas reglas de sintaxis. Este consejo enumera algunas formas cortas de expresar negación y sustracción en Haskell. Avísame si me he perdido algo.Negación
e, solo hazlo-e. Por ejemplo,-length[1,2]da-2.ees incluso moderadamente complejo, necesitará paréntesise, pero generalmente puede guardar un byte moviéndolos:-length(take 3 x)es más corto que-(length$take 3 x).eestá precedido por=un operador infijo de fijación menor que 6, necesita un espacio:f= -2definefyk< -2prueba sikes menor que-2. Si la fijación es 6 o mayor, necesita parens:2^^(-2)da0.25. Por lo general, puede reorganizar las cosas para deshacerse de estos: por ejemplo, hacer en-k>2lugar dek< -2.!es un operador, entonces-a!bse analiza como(-a)!bsi la fijeza de!a lo sumo sea 6 (por lo que-1<1daTrue), y de lo-(a!b)contrario (por lo que-[1,2]!!0da-1). La fijación predeterminada de los operadores definidos por el usuario y las funciones de retroceso es 9, por lo que siguen la segunda regla.mapetc.), use la sección(0-).Sustracción
k, use la sección(-k+), que suma-k.kIncluso puede ser una expresión bastante compleja:(-2*length x+)funciona como se esperaba.preden su lugar, a menos que requiera un espacio en ambos lados. Esto es raro y generalmente ocurre conuntiluna función definida por el usuario, ya quemap pred xpuede ser reemplazada porpred<$>xyiterate pred xpor[x,x-1..]. Y si tienef pred xalgún lugar, probablemente debería definirlofcomo una función infija de todos modos. Mira este consejo .fuente
Intente reorganizar definiciones y / o argumentos de funciones
A veces puede guardar un par de bytes cambiando el orden de los casos de coincidencia de patrones en una definición de función. Estos ahorros son baratos, pero fáciles de pasar por alto.
Como ejemplo, considere la siguiente versión anterior de (una parte de) esta respuesta :
Esta es una definición recursiva de
?, con el caso base como la lista vacía. Como[]no es un valor útil, debemos intercambiar las definiciones y reemplazarlo con el comodín_o un argumento ficticioy, guardando un byte:De la misma respuesta, considere esta definición:
La lista vacía aparece en el valor de retorno, por lo que podemos guardar dos bytes intercambiando los casos:
Además, el orden de los argumentos de la función a veces puede marcar la diferencia al permitirle eliminar espacios en blanco innecesarios. Considere una versión anterior de esta respuesta :
Hay un molesto espacio en blanco entre
hypen la primera rama. Podemos deshacernos de él definiendo enh a p qlugar deh p q a:fuente
No malgastes la guardia "de lo contrario"
Un guardia final que es un comodín
True(más corto como1>0) puede usarse para unir una variable. Comparar:Dado que la guardia es obligatoria y de lo contrario se desperdicia, se necesita poco para que valga la pena. Es suficiente para guardar un par de parens o vincular una expresión de longitud 3 que se usa dos veces. A veces puede negar guardias para hacer que el caso final sea la expresión que mejor utiliza un enlace.
fuente
Usar en
,lugar de&&en guardiasSe pueden combinar múltiples condiciones en un guardia que todos deben tener en
,lugar de&&.fuente
f xs m | [x] <- xs, Just y <- m, x > 3 = ySintaxis más corta para declaraciones locales
A veces necesita definir una función u operador local, pero cuesta muchos bytes escribir
whereolet…inelevarlo al nivel superior agregando argumentos adicionales.Afortunadamente, Haskell tiene una sintaxis confusa y poco utilizada pero razonablemente breve para las declaraciones locales :
En este caso:
Puede usar esta sintaxis con declaraciones de múltiples declaraciones o declaraciones múltiples, e incluso anida:
También funciona para vincular variables u otros patrones, aunque los protectores de patrones tienden a ser más cortos para eso a menos que también esté vinculando funciones.
fuente
[f 1|let f x=x+1].Evitar
repeat nCualquiera de esas cuatro expresiones producirá una lista infinita de
n's.Es un consejo muy específico, ¡pero puede ahorrar hasta 3 bytes!
fuente
nes global,l=n:l;ltiene la misma longitud y funciona para (algunas) expresiones más largas. (Aunque puede necesitar espacios en blanco).Condicionales más cortos cuando un resultado es la lista vacía
Cuando necesita un condicional que devuelve la lista
Ao la lista vacía[]según alguna condiciónC, existen algunas alternativas más cortas a las construcciones condicionales habituales:Ejemplos: 1 , 2
fuente
Ay[]cambió.*>tiene una mayor fijación que>>(todavía un poco baja para la comodidad.)Reglas de análisis de Lambda
Una expresión lambda en realidad no necesita paréntesis a su alrededor, simplemente toma con avidez todo para que todo se analice, por ejemplo, hasta
(foo$ \x -> succ x)let a = \x -> succ x in a 4main = getContents>>= \x -> head $ words xse encuentra, y hay algunos casos extraños en los que esto puede ahorrarle un byte o dos. Creo que
\también se puede usar para definir operadores, por lo que al explotar esto necesitará un espacio al escribir una lambda directamente después de un operador (como en el tercer ejemplo).Aquí hay un ejemplo de dónde usar una lambda fue lo más corto que pude resolver. El código básicamente se ve así:
fuente
Reemplazar
letpor lambdaEsto generalmente puede acortar una definición auxiliar solitaria que no puede vincularse con una protección o definirse globalmente por alguna razón. Por ejemplo, reemplazar
por los 3 bytes más cortos
Para múltiples definiciones auxiliares, la ganancia es probablemente menor, dependiendo del número de definiciones.
Si algunas de las definiciones se refieren a las demás, es aún más difícil guardar bytes de esta manera:
La advertencia principal con esto es que le
letpermite definir variables polimórficas, pero las lambdas no lo hacen, como lo señaló @ChristianSievers. Por ejemplo,resulta en
(1,1), peroda un error de tipo
fuente
let, por lo que podemos hacerlet f=id in (f 0,f True). Si intentamos reescribir esto con lambda, no escribe check.Atar usando guardias
Al definir una función con nombre, puede vincular una expresión a una variable en un resguardo. Por ejemplo,
hace lo mismo que
Use esto para guardar en expresiones repetidas. Cuando la expresión se usa dos veces, se rompe incluso en la longitud 6, aunque los problemas de espacio y precedencia pueden cambiar eso.
(En el ejemplo, si
sno se usa la variable original , es más corto hacerlopero eso no es cierto para vincular expresiones más complejas).
fuente
Justejemplo me hizo pensar que es para la coincidencia de patrones extraer de un contenedor, en lugar de almacenarlo en una expresión.Usar en
(0<$)lugar delengthpara comparacionesCuando se prueba si una lista
aes más larga que una listab, generalmente se escribiríaSin embargo, reemplazar cada elemento de ambas listas con el mismo valor, por ejemplo
0, y luego comparar esas dos listas puede ser más corto:Pruébalo en línea!
El paréntesis son necesarios porque
<$ya los operadores de comparación (==,>,<=, ...) tienen el mismo nivel de precedencia 4, aunque en algunos otros casos que podrían no ser necesarios, ahorrando aún más bytes.fuente
Más corta
transposePara usar la
transposefunciónData.Listhay que importarla. Si esta es la única función que necesita la importación, se puede guardar un byte utilizando la siguientefoldrdefinición detranspose:Tenga en cuenta que el comportamiento solo es idéntico para una lista de listas con la misma longitud.
Utilicé esto con éxito aquí .
fuente
Consigue sufijos
Use
scanr(:)[]para obtener los sufijos de una lista:Esto es mucho más corto que
tailsdespuésimport Data.List. Puedes hacer prefijos conscanr(\_->init)=<<id(encontrado por Ørjan Johansen).Esto ahorra un byte sobre
fuente
scanl(flip(:))[] "abc"=["","a","ba","cba"]también vale la pena mencionarlo: a veces los prefijos al revés no importan.scanr(\_->init)=<<id