Que dicas gerais você tem para jogar golfe em Haskell? Estou procurando idéias que possam ser aplicadas para codificar problemas de golfe em geral que sejam pelo menos um pouco específicos para Haskell. Poste apenas uma dica por resposta.
Se você é novo no golfe em Haskell, consulte o Guia de regras de golfe em Haskell . Há também uma sala de bate-papo exclusiva de Haskell: Of Monads and Men .
Respostas:
Definir operadores de infix em vez de funções binárias
Isso economiza geralmente um ou dois espaços por definição ou chamada.
vs.
Os símbolos disponíveis para os operadores de um-byte são
!
,#
,%
,&
, e?
. Todas as outras pontuações ASCII já estão definidas como um operador pelo Prelude (como$
) ou têm um significado especial na sintaxe de Haskell (como@
).Se você precisar de mais de cinco operadores, poderá usar combinações dos itens acima, como
!#
certos caracteres de pontuação Unicode, como estes (todos os 2 bytes em UTF-8):fonte
(x!y)z=x+y*z
e(x#y)z u=x*z+y*u
ambos funcionam como esperado.\f g(!)x y->f g!x y
vez de\f g j x y->j(f g)(x y)
g x=…;g(f x)
é maior do que_?x=…;0!f x
Use notação inútil (ou livre), quando apropriado
Frequentemente, uma função com um ou dois parâmetros pode ser escrita sem pontos.
Portanto, uma pesquisa por uma lista de tuplas cujos elementos são trocados é ingenuamente escrita como:
(o tipo existe apenas para ajudá-lo a entender o que está fazendo.)
para nossos propósitos, isso é muito melhor:
fonte
Use a lista mônada
Uma rápida revisão:
Exemplos:
Repetindo uma lista duas vezes
Mais curta
concatMap
Menor
concat
compreensão da lista +produto cartesiano
Lista de coordenadas em uma treliça
fonte
[0..b]>>[a]
vez dereplicate a b
.a<$[1..b]
é ainda mais curto, porreplicate
.=<<
força você a importarControl.Monad
. Se você não precisar disso por algum outro motivo, trocar os argumentos e usar>>=
parece mais conciso.Data.Traversable
qualquer maneira, o exemplo do produto cartesiano pode ser abreviado parafor["Hh","io",".!"]id
.(=<<)
está no Prelude , na verdade! Eu usei muito isso.Use proteções não condicionais:
Use ponto-e-vírgula e não recuos
Use expressões booleanas para funções booleanas
(SO está sendo uma dor ao me deixar publicá-las separadamente)
fonte
&&
quando estiver dentro de uma compreensão de lista.True
=>1>0
f a=if a>0 then 3 else 7
interact :: (String → String) → IO ()
As pessoas geralmente esquecem que essa função existe - ela pega todo o stdin e a aplica a uma função (pura). Costumo ver
main
-code ao longo das linhas deenquanto
é um pouco mais curto. É no Prelude, portanto não há necessidade de importação!
fonte
Use GHC 7.10
A primeira versão do GHC que continha esse material foi lançada em 27 de março de 2015 .
É a versão mais recente e o Prelude recebeu algumas novas adições úteis para jogar golfe:
Os operadores
(<$>)
e(<*>)
Esses operadores úteis
Data.Applicative
conseguiram!<$>
é apenasfmap
, para que você possa substituirmap f x
efmap f x
porf<$>x
qualquer lugar e recuperar bytes. Além disso,<*>
é útil naApplicative
instância para listas:O
(<$)
operadorx<$a
é equivalente afmap (const x) a
; ou seja, substitua todos os elementos em um contêiner porx
.Geralmente, é uma boa alternativa para
replicate
:4<$[1..n]
é menor quereplicate n 4
.A proposta dobrável / atravessável
As seguintes funções foram retiradas do trabalho em listas
[a]
paraFoldable
tipos geraist a
:Isso significa que agora eles também trabalham
Maybe a
, onde se comportam exatamente como "listas com no máximo um elemento". Por exemplo,,null Nothing == True
ousum (Just 3) == 3
. Da mesma forma,length
retorna 0 paraNothing
e 1 paraJust
valores. Em vez de escrever,x==Just y
você pode escreverelem y x
.Você também pode aplicá-las em tuplas, o que funciona como se você tivesse chamado
\(a, b) -> [b]
primeiro. É quase completamente inútil, masor :: (a, Bool) -> Bool
é um personagem menor quesnd
, eelem b
é menor que(==b).snd
.O Monoid funciona
mempty
emappend
Não é sempre um salva-vidas, mas se você pode inferir o tipo,
mempty
é um byte mais curto queNothing
isso, então é isso.fonte
<*>
entrar no Prelude! Isso deve ser útil mesmo quando não é código de golfe (aplicativo é uma palavra tão longa).[1..2]
dentro. isso é apenas[1,2]
<*
deApplicative
, que para listas éxs <* ys == concatMap (replicate (length ys)) xs
. Isso é diferentexs >> ys
ou oxs *> ys
que éconcat (replicate (length ys)) xs
.pure
que é mais curto tambémreturn
veio neste ponto.<>
vez demappend
, agora é (com GHC 8.4.1) parte doPrelude
.Use em
1<2
vez deTrue
e em1>2
vez deFalse
.fonte
f=max 10
.if(true)
em outros idiomas. no prelúdio, caso contrário, é realmente o valor booleanoTrue
.otherwise
.Use a compreensão da lista (de maneiras inteligentes)
Todo mundo sabe que é uma sintaxe útil, geralmente menor que
map
+ um lambda:Ou
filter
(e opcionalmente ummap
ao mesmo tempo):Mas existem alguns usos mais esquisitos que são úteis de vez em quando. Por um lado, a compreensão da lista não precisa conter nenhuma
<-
seta:O que significa que, em vez de
if p then[x]else[]
, você pode escrever[x|p]
. Além disso, para contar o número de elementos de uma lista que satisfazem uma condição, você intuitivamente escreveria:Mas isso é mais curto:
fonte
Saber seu
Prelude
Inicie o GHCi e role a documentação do Prelude . Sempre que você cruza uma função que tem um nome abreviado, pode ser útil procurar alguns casos em que possa ser útil.
Por exemplo, suponha que você deseje transformar uma string
s = "abc\ndef\nghi"
em uma que seja separada por espaço"abc def ghi"
. A maneira óbvia é:Mas você pode fazer melhor se abusar
max
e o fato de\n < space < printable ASCII
:Outro exemplo é o
lex :: String -> [(String, String)]
que faz algo bastante misterioso:Tente
fst=<<lex s
obter o primeiro token de uma string, pulando o espaço em branco. Aqui está uma solução inteligente da henkma que usalex.show
emRational
valores.fonte
Corresponder a um valor constante
Uma compreensão de lista pode corresponder a um padrão em uma constante.
Isso extrai os zeros de uma lista
l
, ou seja, faz uma lista de quantos zs existeml
.Isso faz uma lista de quantos
1
são os elementosl
quef
levam para a lista vazia (usando<$>
como infixomap
). Apliquesum
para contar esses elementos.Comparar:
Uma constante pode ser usada como parte de uma correspondência de padrão. Isso extrai as segundas entradas de todas as tuplas cuja primeira entrada é
0
.Observe que tudo isso exige um literal constante constante, não o valor de uma variável. Por exemplo,
let x=1 in [1|x<-[1,2,3]]
será exibido[1,1,1]
, não[1]
, porque ax
ligação externa é substituída.fonte
Use em
words
vez de uma longa lista de strings. Isso não é realmente específico para Haskell, outros idiomas também têm truques semelhantes.fonte
Conheça suas funções monádicas
1)
simule funções monádicas usando
mapM
.muitas vezes o código terá
sequence(map f xs)
, mas pode ser substituído pormapM f xs
. mesmo quando apenas usandosequence
sozinho, é mais longomapM id
.2)
combine funções usando
(>>=)
(ou(=<<)
)a versão da função monad
(>>=)
é definida da seguinte forma:pode ser útil para criar funções que não podem ser expressas como um pipeline. por exemplo,
\x->x==nub x
é maior quenub>>=(==)
e\t->zip(tail t)t
é maior quetail>>=zip
.fonte
Applicative
e nãoMonad
exista a implementaçãopure
, que é mais curta do queconst
e realmente me ajudou antes.Os argumentos podem ser mais curtos que as definições
Acabei de ser enganado de maneira muito curiosa pelo henkma .
Se uma função auxiliar
f
na sua resposta usa um operador que não é usado em nenhum outro lugar da sua resposta ef
é chamada uma vez, faça do operador um argumentof
.Este:
Tem dois bytes a mais que isso:
fonte
Use o operador contras (:)
ao concatenar listas, se o primeiro tiver o comprimento 1, use-o
:
.fonte
1:2:3:x
vez de[1,2,3]++x
.Não use backticks com muita frequência. Os backticks são uma ferramenta interessante para criar seções de funções de prefixo, mas às vezes podem ser mal utilizados.
Uma vez vi alguém escrever esta subexpressão:
Embora seja o mesmo que justo
v x
.Outro exemplo é escrever
(x+1)`div`y
em oposição adiv(x+1)y
.Eu vejo isso acontecer em torno
div
eelem
mais frequentemente porque essas funções são geralmente utilizados como infix em código regular.fonte
Use protetores de padrão
Eles são mais curtos que um
let
ou um lambda que desconstrói os argumentos de uma função que você está definindo. Isso ajuda quando você precisa de algo comofromJust
a partir deData.Maybe
:é maior que
é maior que
é maior que
Na verdade, eles são mais curtos, mesmo quando vinculam um valor antigo simples em vez de desconstruir: veja a dica do xnor .
fonte
e
não é realmente um token, mas uma expressão mais longa que precisa$
antes dele, o que geralmente é o caso.Condicional mais curto
é equivalente a
Veja como funciona:
fonte
if b then y else x
?bool
ser mais curto que você não precisa de uma compreensão da listaTrabalhando com o sinal de menos
O sinal de menos
-
é uma exceção irritante para muitas regras de sintaxe. Esta dica lista algumas maneiras curtas de expressar negação e subtração em Haskell. Por favor, deixe-me saber se eu perdi alguma coisa.Negação
e
, apenas faça-e
. Por exemplo,-length[1,2]
dá-2
.e
for moderadamente complexo, você precisará de parêntesese
, mas geralmente pode salvar um byte movendo-o:-length(take 3 x)
é menor que-(length$take 3 x)
.e
for precedido por=
ou um operador infix de fixidade menor que 6, você precisará de um espaço:f= -2
definef
ek< -2
testa sek
é menor que-2
. Se a fixidez é de 6 ou maior, você precisa de parênteses:2^^(-2)
dá0.25
. Geralmente, você pode reorganizar as coisas para se livrar delas: por exemplo, faça em-k>2
vez dek< -2
.!
é um operador, então-a!b
é analisado como(-a)!b
se a fixidez de!
fosse no máximo 6 (o que-1<1
dáTrue
) e o-(a!b)
contrário (o que-[1,2]!!0
dá-1
). A fixidez padrão dos operadores definidos pelo usuário e das funções com reticulação é 9, portanto eles seguem a segunda regra.map
etc), utilizar a secção(0-)
.Subtração
k
, use a seção(-k+)
que adiciona-k
.k
pode até ser uma expressão bastante complexa:(-2*length x+)
funciona como esperado.pred
vez disso, a menos que exija espaço nos dois lados. Isso é raro e geralmente ocorre comuntil
ou uma função definida pelo usuário, poismap pred x
pode ser substituída porpred<$>x
eiterate pred x
por[x,x-1..]
. E se você temf pred x
algum lugar, provavelmente deve definirf
como uma função infix de qualquer maneira. Veja esta dica .fonte
Tente reorganizar definições de função e / ou argumentos
Às vezes, você pode salvar alguns bytes alterando a ordem dos casos de correspondência de padrão em uma definição de função. Essas economias são baratas, mas fáceis de ignorar.
Como exemplo, considere a seguinte versão anterior (parte da) desta resposta :
Esta é uma definição recursiva de
?
, com o caso base sendo a lista vazia. Como[]
não é um valor útil, devemos trocar as definições e substituí-lo pelo caractere curinga_
ou argumento fictícioy
, salvando um byte:Da mesma resposta, considere esta definição:
A lista vazia ocorre no valor de retorno, para que possamos salvar dois bytes trocando os casos:
Além disso, a ordem dos argumentos das funções às vezes pode fazer a diferença, permitindo remover espaços em branco desnecessários. Considere uma versão anterior desta resposta :
Há um espaço irritante de espaço em branco entre
h
ep
no primeiro ramo. Podemos nos livrar dele definindo emh a p q
vez deh p q a
:fonte
Não desperdice a guarda "caso contrário"
Um guarda final que é genérico
True
(menor que1>0
) pode ser usado para vincular uma variável. Comparar:Como a guarda é obrigatória e é desperdiçada, pouco é necessário para fazer isso valer a pena. Basta salvar um par de parênteses ou vincular uma expressão de comprimento 3 usada duas vezes. Às vezes, você pode negar guardas para tornar o caso final a expressão que melhor usa uma ligação.
fonte
Use em
,
vez de&&
em guardasMúltiplas condições em um guarda que todos precisam manter podem ser combinadas em
,
vez de&&
.fonte
f xs m | [x] <- xs, Just y <- m, x > 3 = y
Sintaxe mais curta para declarações locais
Às vezes, você precisa definir uma função ou operador local, mas custa muitos bytes para escrever
where
oulet…in
ou elevá-la ao nível superior, adicionando argumentos extras.Felizmente, Haskell tem uma sintaxe confusa e raramente usada, mas razoavelmente concisa para declarações locais :
Nesse caso:
Você pode usar esta sintaxe com declarações com várias instruções ou várias declarações, e até aninha:
Ele também funciona para vincular variáveis ou outros padrões, embora os protetores de padrões tendam a ser menores para isso, a menos que você também esteja vinculando funções.
fonte
[f 1|let f x=x+1]
.Evitar
repeat n
Qualquer uma dessas quatro expressões produzirá uma lista infinita de
n
's.É uma dica muito específica, mas pode economizar até 3 bytes!
fonte
n
for global,l=n:l;l
tem o mesmo comprimento e funciona para (algumas) expressões mais longas. (Pode precisar de espaço em branco.) #Condicionais mais curtos quando um resultado é a lista vazia
Quando você precisa de um condicional que retorna a lista
A
ou a lista vazia,[]
dependendo de alguma condiçãoC
, existem algumas alternativas mais curtas às construções condicionais usuais:Exemplos: 1 , 2
fonte
A
e[]
mudou.*>
tem fixidez maior do que>>
(ainda um pouco baixo para o conforto.)Regras de análise do Lambda
Uma expressão lambda na verdade não precisa de parênteses em torno dela - apenas pega avidamente tudo, de modo que a coisa toda ainda analisa, por exemplo, até
(foo$ \x -> succ x)
let a = \x -> succ x in a 4
main = getContents>>= \x -> head $ words x
é encontrado e existem alguns casos estranhos em que isso pode economizar um ou dois bytes. Eu acredito
\
que também pode ser usado para definir operadores, portanto, ao explorar isso, você precisará de um espaço ao escrever um lambda diretamente após um operador (como no terceiro exemplo).Aqui está um exemplo de onde o uso de um lambda foi a coisa mais curta que consegui descobrir. O código basicamente se parece com:
fonte
Substituir
let
por lambdaIsso geralmente pode reduzir uma definição auxiliar isolada que não pode ser vinculada a um guarda ou definida globalmente por algum motivo. Por exemplo, substitua
pelos 3 bytes mais curtos
Para várias definições auxiliares, o ganho é provavelmente menor, dependendo do número de definições.
Se algumas das definições se referirem às outras, é ainda mais difícil salvar bytes desta maneira:
A principal ressalva disso é que
let
permite definir variáveis polimórficas, mas as lambdas não, como observado por @ChristianSievers. Por exemplo,resulta em
(1,1)
, masdá um erro de tipo.
fonte
let
, então podemos fazerlet f=id in (f 0,f True)
. Se tentarmos reescrever isso com o lambda, não digite check.Vincular usando proteções
Ao definir uma função nomeada, você pode vincular uma expressão a uma variável em um guarda. Por exemplo,
faz o mesmo que
Use isso para economizar em expressões repetidas. Quando a expressão é usada duas vezes, ela atinge o comprimento 6, embora os problemas de espaçamento e precedência possam mudar isso.
(No exemplo, se a variável original
s
não for usada, será mais curtomas isso não é verdade para vincular expressões mais complexas.)
fonte
Just
exemplo me fez pensar que é para correspondência de padrões extrair de um contêiner, em vez de armazenar em uma expressão.Use em
(0<$)
vez delength
para comparaçõesAo testar se uma lista
a
é maior que uma listab
, normalmente se escreveNo entanto, a substituição de cada elemento de ambas as listas pelo mesmo valor, por exemplo
0
, e a comparação dessas duas listas podem ser mais curtas:Experimente online!
O parêntese são necessários porque
<$
e os operadores de comparação (==
,>
,<=
, ...) têm o mesmo nível de precedência 4, embora em alguns outros casos, pode não ser necessário, economizando ainda mais bytes.fonte
Mais curta
transpose
Para usar a
transpose
funçãoData.List
precisa ser importada. Se esta é a única função que precisa da importação, é possível salvar um byte usando a seguintefoldr
definição detranspose
:Observe que o comportamento é idêntico apenas para uma lista de listas com o mesmo comprimento.
Eu usei isso aqui com sucesso .
fonte
Obter sufixos
Use
scanr(:)[]
para obter os sufixos de uma lista:Isso é muito mais curto do que
tails
depoisimport Data.List
. Você pode fazer prefixos comscanr(\_->init)=<<id
(encontrado por Ørjan Johansen).Isso economiza um byte
fonte
scanl(flip(:))[] "abc"
=["","a","ba","cba"]
também mereça ser mencionado - algumas vezes os prefixos que estão ao contrário não importam.scanr(\_->init)=<<id