Какие общие советы у вас есть для игры в гольф на Хаскеле? Я ищу идеи, которые могут быть применены к задачам по коду для гольфа в целом, которые, по крайней мере, несколько специфичны для Haskell. Пожалуйста, оставьте только один совет за ответ.
Если вы новичок в игре в гольф на Хаскелле, ознакомьтесь с Руководством по правилам игры в гольф на Хаскелле . Также есть специальный чат на Хаскелле: Монады и Мужчины .
Ответы:
Определите инфиксные операторы вместо бинарных функций
Это обычно экономит один или два пробела на определение или вызов.
против
Доступные символы для операторов 1 байт являются
!
,#
,%
,&
и?
. Вся остальная пунктуация ASCII либо уже определена как оператор Prelude (например,$
), либо имеет особое значение в синтаксисе Haskell (например,@
).Если вам нужно более пяти операторов, вы можете использовать комбинации из вышеперечисленного, например
!#
, или определенные знаки препинания в Юникоде, такие как эти (все 2 байта в UTF-8):источник
(x!y)z=x+y*z
и(x#y)z u=x*z+y*u
оба работают как положено.\f g(!)x y->f g!x y
вместо\f g j x y->j(f g)(x y)
g x=…;g(f x)
длиннее, чем_?x=…;0!f x
Используйте бессмысленные (или -без) нотации, где это уместно
Часто функция с одним или двумя параметрами может быть написана без указания точки.
Так что поиск списка кортежей, чьи элементы поменялись местами, наивно написан так:
(тип предназначен только для того, чтобы помочь вам понять, что он делает.)
для наших целей это намного лучше:
источник
Используйте список монад
Краткий обзор:
Примеры:
Повторение списка дважды
Более короткие
concatMap
Короче
concat
+ список пониманияДекартово произведение
Список координат на решетке
источник
[0..b]>>[a]
вместоreplicate a b
.a<$[1..b]
еще короче, дляreplicate
.=<<
заставляет вас импортироватьControl.Monad
. Если вам это не нужно по какой-то другой причине, поменяйте местами аргументы и используйте их,>>=
как представляется, более краткими.Data.Traversable
все равно нужно , пример декартового произведения можно сократить доfor["Hh","io",".!"]id
.(=<<)
на самом деле в прелюдии ! Я использовал это много.Используйте охрану не условно:
Используйте точки с запятой, а не отступы
Используйте логические выражения для логических функций
(ТАК больно позволять мне публиковать их отдельно)
источник
&&
когда внутри списка понимания.True
=>1>0
f a=if a>0 then 3 else 7
interact :: (String → String) → IO ()
Люди часто забывают, что эта функция существует - она захватывает весь stdin и применяет ее к (чистой) функции. Я часто вижу
main
-кодпока
немного короче. Это в Prelude, поэтому нет необходимости в импорте!
источник
Используйте GHC 7.10
Первая версия GHC, которая содержала этот материал, была выпущена 27 марта 2015 года .
Это последняя версия, и Prelude получил несколько новых дополнений, которые полезны для игры в гольф:
(<$>)
И(<*>)
операторыЭти полезные операторы из
Data.Applicative
сделали это в!<$>
это простоfmap
, так что вы можете заменитьmap f x
иfmap f x
сf<$>x
везде и отыгрывать байты. Также<*>
полезно вApplicative
экземпляре для списков:(<$)
операторx<$a
эквивалентноfmap (const x) a
; т.е. заменить каждый элемент в контейнере наx
.Часто это хорошая альтернатива
replicate
:4<$[1..n]
корочеreplicate n 4
.Складное / проходимое предложение
Следующие функции были отменены при работе со списками
[a]
для общихFoldable
типовt a
:Это означает, что теперь они также работают
Maybe a
, где ведут себя так же, как «списки, содержащие не более одного элемента». Напримерnull Nothing == True
, илиsum (Just 3) == 3
. Аналогично,length
возвращает 0 дляNothing
и 1 дляJust
значений. Вместо того, чтобы писать,x==Just y
вы можете написатьelem y x
.Вы также можете применять их к кортежам, которые работают так, как если бы вы звонили
\(a, b) -> [b]
первыми. Это почти полностью бесполезно, ноor :: (a, Bool) -> Bool
на один символ меньше, чемsnd
иelem b
меньше(==b).snd
.Моноидные функции
mempty
иmappend
Не часто спасатель, но если вы можете определить тип,
mempty
на один байт корочеNothing
, значит, так и есть.источник
<*>
превращении его в Prelude! Это должно быть полезно, даже если это не код гольф (аппликативное слово такое длинное).[1..2]
там. это просто[1,2]
<*
отApplicative
, которые для списковxs <* ys == concatMap (replicate (length ys)) xs
. Это отличается от ,xs >> ys
илиxs *> ys
которыйconcat (replicate (length ys)) xs
.pure
корочеreturn
пришло тоже в этот момент.<>
вместоmappend
, теперь это (с GHC 8.4.1) частьPrelude
.Используйте
1<2
вместоTrue
и1>2
вместоFalse
.источник
f=max 10
.if(true)
на других языках. в прелюдии, иначе на самом деле это логическое значениеTrue
.otherwise
.Используйте списочные представления (умными способами)
Все знают, что они полезны синтаксис, часто короче, чем
map
+ лямбда:Или
filter
(и, возможно,map
одновременно):Но есть и более странные способы использования, которые могут пригодиться время от времени. Во-первых, понимание списка не должно содержать никаких
<-
стрелок вообще:Что означает, вместо
if p then[x]else[]
, вы можете написать[x|p]
. Кроме того, чтобы подсчитать количество элементов списка, удовлетворяющих условию, вы должны написать:Но это короче
источник
Знать ваши
Prelude
Запустите GHCi и пролистайте документацию Prelude . Всякий раз, когда вы пересекаете функцию, которая имеет короткое имя, она может окупиться, чтобы найти некоторые случаи, когда она может быть полезна.
Например, предположим, что вы хотите преобразовать строку
s = "abc\ndef\nghi"
в строку , разделенную пробелами"abc def ghi"
. Очевидный путь:Но вы можете сделать лучше, если вы злоупотребляете
max
, и тот факт, что\n < space < printable ASCII
:Другой пример
lex :: String -> [(String, String)]
, который делает что-то довольно загадочное:Попробуйте
fst=<<lex s
получить первый токен из строки, пропуская пробелы. Вот это умное решение по henkma , который используетlex.show
наRational
значения.источник
Соответствует постоянному значению
Понимание списка может соответствовать шаблону по константе.
Это извлекает 0 из списка
l
, то есть создает список из 0, которые есть вl
.Это делает список из столько
1
, сколько есть элементов,l
которые переходятf
в пустой список (используется<$>
как инфиксmap
). Применитьsum
для подсчета этих элементов.Для сравнения:
Константа может использоваться как часть сопоставления с образцом. Это извлекает вторые записи всех кортежей, первая запись которых
0
.Обратите внимание, что все они требуют фактического константного литерала, а не значения переменной. Например,
let x=1 in [1|x<-[1,2,3]]
будет выводить[1,1,1]
, а не[1]
потому, что внешняяx
привязка перезаписана.источник
Используйте
words
вместо длинного списка строк. Это не совсем характерно для Haskell, другие языки тоже имеют схожие приемы.источник
Знай свои монадические функции
1)
имитировать монадические функции с помощью
mapM
.много раз код будет
sequence(map f xs)
, но его можно заменить наmapM f xs
. даже если использоватьsequence
его в одиночку, это дольшеmapM id
.2)
объединить функции, используя
(>>=)
(или(=<<)
)версия монады функции
(>>=)
определяется так:это может быть полезно для создания функций, которые не могут быть выражены в виде конвейера. например,
\x->x==nub x
длиннееnub>>=(==)
и\t->zip(tail t)t
длиннееtail>>=zip
.источник
Applicative
а неMonad
ее реализация,pure
которая корочеconst
и фактически помогла мне раньше.Аргументы могут быть короче, чем определения
Я только что очень любопытным образом переиграл Хенкму .
Если вспомогательная функция
f
в вашем ответе использует оператор, который не используется в другом месте вашего ответа иf
вызывается один раз, сделайте оператор аргументомf
.Этот:
На два байта длиннее этого:
источник
Используйте оператор "против" (:)
при объединении списков, если первый имеет длину 1, используйте
:
вместо него.источник
1:2:3:x
вместо[1,2,3]++x
.Не используйте спины слишком часто. Обратные метки - отличный инструмент для создания разделов префиксных функций, но иногда ими можно злоупотреблять.
Однажды я увидел, как кто-то написал это подвыражение:
Хотя это так же, как и просто
v x
.Другой пример пишет
(x+1)`div`y
в отличие отdiv(x+1)y
.Я вижу, что это происходит вокруг
div
иelem
чаще, потому что эти функции обычно используются как инфикс в обычном коде.источник
Используйте шаблон охранников
Они короче, чем
let
лямбда или лямбда, которая деконструирует аргументы функции, которую вы определяете. Это помогает , когда вам нужно что - то вродеfromJust
отData.Maybe
:длиннее чем
длиннее чем
длиннее чем
На самом деле они короче даже при связывании простого старого значения вместо деконструкции: см . Совет xnor .
источник
e
что на самом деле это не один токен, а более длинное выражение, которое нужно$
перед ним, как это обычно бывает.Короче условно
эквивалентно
Вот как это работает:
источник
if b then y else x
?bool
будет ли короче, потому что вам не нужно понимание спискаРабота со знаком минус
Знак минус
-
является раздражающим исключением из многих правил синтаксиса. Этот совет перечисляет некоторые короткие способы выражения отрицания и вычитания в Haskell. Пожалуйста, дайте мне знать, если я что-то пропустил.Отрицание
e
, просто сделайте-e
. Так , например,-length[1,2]
дает-2
.e
это даже умеренно сложно, вам понадобятся круглые скобкиe
, но обычно вы можете сохранить байт, переместив их:-length(take 3 x)
короче, чем-(length$take 3 x)
.e
перед=
или инфиксным оператором фиксированности меньше 6, вам нужен пробел:f= -2
определяетf
иk< -2
тестирует, еслиk
меньше-2
. Если фиксированность 6 или выше, вам нужны паренсы:2^^(-2)
дает0.25
. Обычно вы можете изменить порядок вещей, чтобы избавиться от них: например, сделать-k>2
вместоk< -2
.!
является оператором, то-a!b
анализируется, как(-a)!b
если бы фиксированность была!
не больше 6 (так-1<1
даетTrue
), и в-(a!b)
противном случае (так-[1,2]!!0
дает-1
). По умолчанию фиксированные пользовательские операторы и функции с обратными метками имеют значение 9, поэтому они следуют второму правилу.map
т. Д.), Используйте раздел(0-)
.Вычитание
k
, используйте раздел(-k+)
, который добавляет-k
.k
может даже быть довольно сложным выражением:(-2*length x+)
работает как положено.pred
вместо этого, если это не потребует пробела с обеих сторон. Это редкое и обычно случается сuntil
или определяемой пользователем функции, так какmap pred x
можно заменитьpred<$>x
иiterate pred x
на[x,x-1..]
. И если у вас естьf pred x
где-то, выf
все равно должны определить как инфиксную функцию. Смотрите этот совет .источник
Попробуйте переставить определения функций и / или аргументы
Иногда вы можете сохранить пару байтов, изменив порядок сопоставления с образцом в определении функции. Эти сбережения дешевы, но их легко не заметить.
В качестве примера рассмотрим следующую более раннюю версию (часть) этого ответа :
Это рекурсивное определение
?
, базовый случай - пустой список. Поскольку[]
это бесполезное значение, мы должны поменять определения и заменить их подстановочным знаком_
или фиктивным аргументомy
, сохранив байт:Из того же ответа рассмотрим это определение:
Пустой список встречается в возвращаемом значении, поэтому мы можем сохранить два байта, поменяв местами регистры:
Кроме того, порядок аргументов функции может иногда иметь значение, позволяя вам удалить ненужные пробелы. Рассмотрим более раннюю версию этого ответа :
Между первой
h
иp
первой ветвями есть досадный кусок пробела . Мы можем избавиться от этого, определивh a p q
вместоh p q a
:источник
Не тратьте "иначе" охрану
Последний защитный элемент, который является универсальным
True
(короче1>0
), может использоваться для привязки переменной. Для сравнения:Поскольку охрана является обязательной и в противном случае теряется, мало что нужно, чтобы это стоило того. Достаточно сохранить пару паренов или связать выражение длины 3, которое используется дважды. Иногда вы можете отменить охрану, чтобы в последнем случае было выражение, которое лучше всего использует привязку.
источник
Используйте
,
вместо&&
охранниковНесколько условий в охране, которые все должны держать, могут быть объединены
,
вместо&&
.источник
f xs m | [x] <- xs, Just y <- m, x > 3 = y
Более короткий синтаксис для локальных объявлений
Иногда вам нужно определить локальную функцию или оператор, но это требует много байтов, чтобы написать
where
илиlet…in
или поднять его на верхний уровень, добавив дополнительные аргументы.К счастью, Haskell имеет запутанный и редко используемый, но достаточно краткий синтаксис для локальных объявлений :
В этом случае:
Вы можете использовать этот синтаксис с объявлениями с несколькими операторами или несколькими объявлениями, и он даже вложен:
Это также работает для связывания переменных или других шаблонов, хотя защита шаблонов, как правило, короче для этого, если вы не являетесь также обязательными функциями.
источник
[f 1|let f x=x+1]
.избежать
repeat n
Любое из этих четырех выражений создаст бесконечный список
n
символов.Это очень специфический совет, но он может сэкономить до 3 байтов!
источник
n
является глобальным,l=n:l;l
имеет такую же длину и работает для (некоторых) более длинных выражений. (Более короткие условия, когда один результат - пустой список
Когда вам нужно условное выражение, которое возвращает список
A
или пустой список в[]
зависимости от некоторого условияC
, тогда существуют некоторые более короткие альтернативы обычным условным конструкциям:Примеры: 1 , 2
источник
A
и[]
переключился.*>
имеет более высокую надежность, чем>>
(все еще немного низко для комфорта.)Правила разбора лямбды
Лямбда-выражению на самом деле не нужны круглые скобки вокруг него - оно просто жадно захватывает все, так что все это по-прежнему анализируется, например, до
(foo$ \x -> succ x)
let a = \x -> succ x in a 4
main = getContents>>= \x -> head $ words x
встречается, и есть некоторые странные крайние случаи, когда это может сэкономить вам один или два байта. Я считаю, что
\
также может использоваться для определения операторов, поэтому при использовании этого вам понадобится пробел при написании лямбда-выражения непосредственно после оператора (как в третьем примере).Вот пример, где использование лямбды было самой короткой вещью, которую я мог понять. Код в основном выглядит так:
источник
Заменить
let
на лямбдуОбычно это может сократить уединенное вспомогательное определение, которое по какой-либо причине не может быть связано с охраной или определено глобально. Например, заменить
на 3 байта короче
Для нескольких вспомогательных определений коэффициент усиления, вероятно, меньше, в зависимости от количества определений.
Если некоторые определения относятся к другим, еще сложнее сохранить байты следующим образом:
Основное предостережение при этом заключается в том, что
let
вы можете определять полиморфные переменные, а лямбда-выражения нет, как отмечает @ChristianSievers. Например,результаты
(1,1)
, нодает ошибку типа.
источник
let
, поэтому мы можем сделатьlet f=id in (f 0,f True)
. Если мы попытаемся переписать это с помощью лямбды, это не проверка типа.Привязать с помощью охранников
При определении именованной функции вы можете связать выражение с переменной в Guard. Например,
делает так же, как
Используйте это, чтобы сэкономить на повторных выражениях. Когда выражение используется дважды, оно безубыточно даже на длине 6, хотя проблемы с пробелами и приоритетами могут изменить это.
(В этом примере, если исходная переменная
s
не используется, ее нужно сделать корочено это не так для связывания более сложных выражений.)
источник
Just
пример заставил меня подумать, что для сопоставления с образцом нужно извлечь из контейнера, а не хранить в выражении.Используйте
(0<$)
вместоlength
для сравненияПри тестировании, если список
a
длиннее спискаb
, обычно пишутОднако замена каждого элемента обоих списков одним и тем же значением, например
0
, а затем сравнение этих двух списков может быть короче:Попробуйте онлайн!
Скобки необходимы , потому что
<$
и операторы сравнения (==
,>
,<=
, ...) имеют одинаковый уровень приоритета 4, хотя в некоторых других случаях они не могут быть необходимы, экономя даже больше байт.источник
Более короткие
transpose
Для использования
transpose
функцииData.List
должен быть импортирован. Если это единственная функция, требующая импорта, можно сохранить байт, используя следующееfoldr
определениеtranspose
:Обратите внимание, что поведение идентично только для списка списков одинаковой длины.
Я успешно использовал это здесь .
источник
Получить суффиксы
Используйте
scanr(:)[]
для получения суффиксов списка:Это намного короче чем
tails
послеimport Data.List
. Вы можете делать префиксы с помощьюscanr(\_->init)=<<id
(найденный Орджаном Йохансеном).Это экономит байт
источник
scanl(flip(:))[] "abc"
=["","a","ba","cba"]
также стоит упомянуть - иногда префиксы в обратном направлении не имеют значения.scanr(\_->init)=<<id