¿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*z
y(x#y)z u=x*z+y*u
ambos funcionan como se esperaba.\f g(!)x y->f g!x y
lugar de\f g j x y->j(f g)(x y)
g x=…;g(f x)
es más largo que_?x=…;0!f x
Use 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
concatMap
Menor
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.Traversable
todos 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>0
f a=if a>0 then 3 else 7
interact :: (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.Applicative
lograron!<$>
es justofmap
, para que pueda reemplazarmap f x
yfmap f x
conf<$>x
todas partes y recuperar bytes. Además,<*>
es útil en laApplicative
instancia para listas:El
(<$)
operadorx<$a
es 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]
aFoldable
tipos 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,length
devuelve 0 paraNothing
y 1 para losJust
valores. En lugar de escribirx==Just y
puedes 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) -> Bool
es un personaje más corto quesnd
, yelem b
es más corto que(==b).snd
.Las funciones monoides
mempty
ymappend
No suele ser un salvavidas, pero si puede inferir el tipo,
mempty
es 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]
<*
deApplicative
que para las listas esxs <* ys == concatMap (replicate (length ys)) xs
. Esto es diferentexs >> ys
o loxs *> ys
que esconcat (replicate (length ys)) xs
.pure
que es más cortoreturn
llegó en este punto también.<>
lugar demappend
, ahora es (con GHC 8.4.1) parte dePrelude
.Use en
1<2
lugar deTrue
y en1>2
lugar 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 opcionalmentemap
a 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
Prelude
Encienda 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
max
y el hecho de que\n < space < printable ASCII
:Otro ejemplo es
lex :: String -> [(String, String)]
, que hace algo bastante misterioso:Intenta
fst=<<lex s
obtener el primer token de una cadena, saltando los espacios en blanco. Aquí hay una solución inteligente de henkma que utilizalex.show
enRational
valores.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
1
como hay elementosl
quef
llevan a la lista vacía (usando<$>
como infijomap
). Aplicarsum
para 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 elx
enlace externo se sobrescribe.fuente
Use en
words
lugar 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 usasequence
solo 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 x
es más largo quenub>>=(==)
, y\t->zip(tail t)t
es más largo quetail>>=zip
.fuente
Applicative
y noMonad
existe la implementaciónpure
, que es más cortaconst
y 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
f
en su respuesta usa un operador que no se usa en otra parte de su respuesta, yf
se 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:x
lugar 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`y
en lugar dediv(x+1)y
.Veo que ocurre alrededor
div
y conelem
mayor 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
let
o una lambda que deconstruye los argumentos de una función que está definiendo. Esto ayuda cuando se necesita algo así comofromJust
desdeData.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
e
realidad 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
?bool
serí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
.e
es 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)
.e
está precedido por=
un operador infijo de fijación menor que 6, necesita un espacio:f= -2
definef
yk< -2
prueba sik
es 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>2
lugar dek< -2
.!
es un operador, entonces-a!b
se analiza como(-a)!b
si la fijeza de!
a lo sumo sea 6 (por lo que-1<1
daTrue
), y de lo-(a!b)
contrario (por lo que-[1,2]!!0
da-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.map
etc.), use la sección(0-)
.Sustracción
k
, use la sección(-k+)
, que suma-k
.k
Incluso puede ser una expresión bastante compleja:(-2*length x+)
funciona como se esperaba.pred
en su lugar, a menos que requiera un espacio en ambos lados. Esto es raro y generalmente ocurre conuntil
una función definida por el usuario, ya quemap pred x
puede ser reemplazada porpred<$>x
yiterate pred x
por[x,x-1..]
. Y si tienef pred x
algún lugar, probablemente debería definirlof
como 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
h
yp
en la primera rama. Podemos deshacernos de él definiendo enh a p q
lugar 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 = y
Sintaxis más corta para declaraciones locales
A veces necesita definir una función u operador local, pero cuesta muchos bytes escribir
where
olet…in
elevarlo 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 n
Cualquiera de esas cuatro expresiones producirá una lista infinita de
n
's.Es un consejo muy específico, ¡pero puede ahorrar hasta 3 bytes!
fuente
n
es global,l=n:l;l
tiene 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
A
o la lista vacía[]
según alguna condiciónC
, existen algunas alternativas más cortas a las construcciones condicionales habituales:Ejemplos: 1 , 2
fuente
A
y[]
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 4
main = getContents>>= \x -> head $ words x
se 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
let
por 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
let
permite 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
s
no se usa la variable original , es más corto hacerlopero eso no es cierto para vincular expresiones más complejas).
fuente
Just
ejemplo 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 delength
para comparacionesCuando se prueba si una lista
a
es 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
transpose
Para usar la
transpose
funciónData.List
hay que importarla. Si esta es la única función que necesita la importación, se puede guardar un byte utilizando la siguientefoldr
definició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
tails
despué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