Советы по игре в гольф в Хаскеле

73

Какие общие советы у вас есть для игры в гольф на Хаскеле? Я ищу идеи, которые могут быть применены к задачам по коду для гольфа в целом, которые, по крайней мере, несколько специфичны для Haskell. Пожалуйста, оставьте только один совет за ответ.


Если вы новичок в игре в гольф на Хаскелле, ознакомьтесь с Руководством по правилам игры в гольф на Хаскелле . Также есть специальный чат на Хаскелле: Монады и Мужчины .

Анимеш 'КОДЕР'
источник
1
Видя количество ответов до сих пор, я сомневаюсь, является ли Haskell даже хорошим языком для игры в гольф кода или нет?
Анимеш 'КОДЕР'
10
Почему только один совет за ответ? Кроме того, каждый язык является хорошим языком для игры в гольф. Просто не всегда ожидайте победы.
unclemeat
6
@unclemeat Таким образом, люди могут поднять хорошие наверх, не голосуя на плохих только потому, что они написаны одним и тем же парнем в одном и том же ответе.
MasterMastic
3
Специальный запрос, сжатие строк.
J Аткин
Это, вероятно, не подходит как ответ, но я все еще хочу добавить это здесь: wiki.haskell.org/Prime_numbers_miscellaneous#One-liners
flawr

Ответы:

45

Определите инфиксные операторы вместо бинарных функций

Это обычно экономит один или два пробела на определение или вызов.

0!(y:_)=y
x!(y:z)=(x-1)!z

против

f 0(y:_)=y
f x(y:z)=f(x-1)z

Доступные символы для операторов 1 байт являются !, #, %, &и ?. Вся остальная пунктуация ASCII либо уже определена как оператор Prelude (например, $), либо имеет особое значение в синтаксисе Haskell (например, @).

Если вам нужно более пяти операторов, вы можете использовать комбинации из вышеперечисленного, например !#, или определенные знаки препинания в Юникоде, такие как эти (все 2 байта в UTF-8):

¡ ¢ £ ¤ ¥ ¦ § ¨ © ¬ ® ¯ ° ± ´ ¶ · ¸ ¿ × ÷
shiona
источник
11
Примечание: это также может использоваться для функций с тремя или более аргументами. (x!y)z=x+y*zи (x#y)z u=x*z+y*uоба работают как положено.
Згарб
3
Это также может быть использовано для аргументов функции, например, \f g(!)x y->f g!x yвместо\f g j x y->j(f g)(x y)
Esolanging Fruit
2
Иногда полезно заменить унарные функции на бинарные операторы, если в противном случае вам придется использовать круглые скобки - g x=…;g(f x)длиннее, чем_?x=…;0!f x
Angs
29

Используйте бессмысленные (или -без) нотации, где это уместно

Часто функция с одним или двумя параметрами может быть написана без указания точки.

Так что поиск списка кортежей, чьи элементы поменялись местами, наивно написан так:

revlookup :: Eq b => b -> [(a, b)] -> Maybe a
revlookup e l=lookup e(map swap l)

(тип предназначен только для того, чтобы помочь вам понять, что он делает.)

для наших целей это намного лучше:

revlookup=(.map swap).lookup
shiona
источник
28

Используйте список монад

Краткий обзор:

xs >> ys        =  concat $ replicate (length xs) ys
xs >>= f        =  concatMap f xs
mapM id[a,b,c]  =  cartesian product of lists: a × b × c
mapM f[a,b,c]   =  cartesian product of lists: f a × f b × f c

Примеры:

  • Повторение списка дважды

    Prelude> "aa">>[1..5]
    [1,2,3,4,5,1,2,3,4,5]
    
  • Более короткие concatMap

    Prelude> reverse=<<["Abc","Defgh","Ijkl"]
    "cbAhgfeDlkjI"
    
  • Короче concat+ список понимания

    Prelude> do x<-[1..5];[1..x]
    [1,1,2,1,2,3,1,2,3,4,1,2,3,4,5]
    
  • Декартово произведение

    Prelude> mapM id["Hh","io",".!"]
    ["Hi.","Hi!","Ho.","Ho!","hi.","hi!","ho.","ho!"]
    
  • Список координат на решетке

    Prelude> mapM(\x->[0..x])[3,2]
    [[0,0],[0,1],[0,2],[1,0],[1,1],[1,2],[2,0],[2,1],[2,2],[3,0],[3,1],[3,2]]
    
Линн
источник
1
Другое использование, которое я нашел полезным [0..b]>>[a]вместо replicate a b.
Пшеничный волшебник
3
@WheatWizard a<$[1..b]еще короче, для replicate.
Линн
Использование =<<заставляет вас импортировать Control.Monad. Если вам это не нужно по какой-то другой причине, поменяйте местами аргументы и используйте их, >>=как представляется, более краткими.
dfeuer
OTOH, если вам Data.Traversableвсе равно нужно , пример декартового произведения можно сократить до for["Hh","io",".!"]id.
dfeuer
2
(=<<)на самом деле в прелюдии ! Я использовал это много.
Линн
28

Используйте охрану не условно:

f a=if a>0 then 3 else 7
g a|a>0=3|True=7

Используйте точки с запятой, а не отступы

f a=do
  this
  that
g a=do this;that

Используйте логические выражения для логических функций

f a=if zzz then True else f yyy
g a=zzz||f yyy

(ТАК больно позволять мне публиковать их отдельно)

bazzargh
источник
2
Кроме того, используйте несколько охранников вместо того, &&когда внутри списка понимания.
Джон Дворжак
Хороший Ян, вы должны сделать это в ответ, я буду голосовать за него
bazzargh
5
Первый пример может быть дополнительно сокращен на True=>1>0
Джон Дворак
1
в первом примере, я полагаю, вы имеете в видуf a=if a>0 then 3 else 7
Cyoce
Охрана даже работает, если в этом нет аргументов.
Akangka
24

interact :: (String → String) → IO ()

Люди часто забывают, что эта функция существует - она ​​захватывает весь stdin и применяет ее к (чистой) функции. Я часто вижу main-код

main=getContents>>=print.foo

пока

main=interact$show.foo

немного короче. Это в Prelude, поэтому нет необходимости в импорте!

Flonk
источник
24

Используйте GHC 7.10

Первая версия GHC, которая содержала этот материал, была выпущена 27 марта 2015 года .

Это последняя версия, и Prelude получил несколько новых дополнений, которые полезны для игры в гольф:

(<$>)И (<*>)операторы

Эти полезные операторы из Data.Applicativeсделали это в! <$>это просто fmap, так что вы можете заменить map f xи fmap f xс f<$>xвезде и отыгрывать байты. Также <*>полезно в Applicativeэкземпляре для списков:

Prelude> (,)<$>[1..2]<*>"abcd"
[(1,'a'),(1,'b'),(1,'c'),(1,'d'),(2,'a'),(2,'b'),(2,'c'),(2,'d')]

(<$)оператор

x<$aэквивалентно fmap (const x) a; т.е. заменить каждый элемент в контейнере на x.

Часто это хорошая альтернатива replicate: 4<$[1..n]короче replicate n 4.

Складное / проходимое предложение

Следующие функции были отменены при работе со списками [a]для общих Foldableтипов t a:

fold*, null, length, elem, maximum, minimum, sum, product
and, or, any, all, concat, concatMap

Это означает, что теперь они также работают 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, значит, так и есть.

Линн
источник
5
+1 Приятно слышать о '<$> `и <*>превращении его в Prelude! Это должно быть полезно, даже если это не код гольф (аппликативное слово такое длинное).
Анк-Морпорк
Предупреждение о замене квартиры: если ваша языковая версия новее, чем задача, ваше решение не имеет права на победу. Если вы хотите обновить существующие ответы или ответ, не перезаписывайте существующее решение. Написать приложение.
Джон Дворжак
4
Забавно [1..2]там. это просто[1,2]
гордый haskeller
2
В той же версии мы также получили <*от Applicative, которые для списков xs <* ys == concatMap (replicate (length ys)) xs. Это отличается от , xs >> ysили xs *> ysкоторый concat (replicate (length ys)) xs. pureкороче returnпришло тоже в этот момент.
Angs
2
Теперь вы можете использовать <>вместо mappend, теперь это (с GHC 8.4.1) часть Prelude.
ბიმო
22

Используйте 1<2вместо Trueи 1>2вместо False.

g x|x<10=10|True=x
f x|x<10=10|1<2=x
Flonk
источник
3
Это на самом деле не специфично для Haskell: оно применимо практически ко всем языкам с булевым типом, в отличие от истинных и ложных значений других типов.
Питер Тейлор
Кто-нибудь может объяснить это?
MasterMastic
2
это не хороший пример, я бы просто играл в гольф как f=max 10.
гордый haskeller
@MasterMastic это просто писать if(true)на других языках. в прелюдии, иначе на самом деле это логическое значение True.
гордый haskeller
2
@PeterTaylor Я думаю, что это все еще ценно для новых хаскеллианов (таких как я), как я впервые научился использовать otherwise.
flawr
20

Используйте списочные представления (умными способами)

Все знают, что они полезны синтаксис, часто короче, чем map+ лямбда:

Prelude> [[1..x]>>show x|x<-[1..9]]
["1","22","333","4444","55555","666666","7777777","88888888","999999999"]

Или filter(и, возможно, mapодновременно):

Prelude> [show x|x<-[1..60],mod 60x<1]
["1","2","3","4","5","6","10","12","15","20","30","60"]

Но есть и более странные способы использования, которые могут пригодиться время от времени. Во-первых, понимание списка не должно содержать никаких <-стрелок вообще:

Prelude> [1|False]
[]
Prelude> [1|True]
[1]

Что означает, вместо if p then[x]else[], вы можете написать [x|p]. Кроме того, чтобы подсчитать количество элементов списка, удовлетворяющих условию, вы должны написать:

length$filter p x

Но это короче

sum[1|y<-x,p y]
Линн
источник
Я действительно использовал все это раньше, но я не думал помещать их сюда.
гордый haskeller
18

Знать ваши Prelude

Запустите GHCi и пролистайте документацию Prelude . Всякий раз, когда вы пересекаете функцию, которая имеет короткое имя, она может окупиться, чтобы найти некоторые случаи, когда она может быть полезна.

Например, предположим, что вы хотите преобразовать строку s = "abc\ndef\nghi"в строку , разделенную пробелами "abc def ghi". Очевидный путь:

unwords$lines s

Но вы можете сделать лучше, если вы злоупотребляете max, и тот факт, что \n < space < printable ASCII:

max ' '<$>s

Другой пример lex :: String -> [(String, String)], который делает что-то довольно загадочное:

Prelude> lex "   some string of Haskell tokens  123  "
[("some"," string of Haskell tokens  123  ")]

Попробуйте fst=<<lex sполучить первый токен из строки, пропуская пробелы. Вот это умное решение по henkma , который использует lex.showна Rationalзначения.

Линн
источник
16

Соответствует постоянному значению

Понимание списка может соответствовать шаблону по константе.


[0|0<-l]

Это извлекает 0 из списка l, то есть создает список из 0, которые есть в l.


[1|[]<-f<$>l] 

Это делает список из столько 1, сколько есть элементов, lкоторые переходят fв пустой список (используется <$>как инфикс map). Применить sumдля подсчета этих элементов.

Для сравнения:

[1|[]<-f<$>l]
[1|x<-l,f x==[]]

[x|(0,x)<-l]

Константа может использоваться как часть сопоставления с образцом. Это извлекает вторые записи всех кортежей, первая запись которых 0.


Обратите внимание, что все они требуют фактического константного литерала, а не значения переменной. Например, let x=1 in [1|x<-[1,2,3]]будет выводить [1,1,1], а не [1]потому, что внешняя xпривязка перезаписана.

XNOR
источник
14

Используйте wordsвместо длинного списка строк. Это не совсем характерно для Haskell, другие языки тоже имеют схожие приемы.

["foo","bar"]
words"foo bar"  -- 1 byte longer
["foo","bar","baz"]
words"foo bar baz"  -- 1 byte shorter
["foo","bar","baz","qux"]
words"foo bar baz qux"    -- 3 bytes shorter
nyuszika7h
источник
14

Знай свои монадические функции

1)
имитировать монадические функции с помощью mapM.

много раз код будет sequence(map f xs), но его можно заменить на mapM f xs. даже если использовать sequenceего в одиночку, это дольше mapM id.

2)
объединить функции, используя (>>=)(или (=<<))

версия монады функции (>>=)определяется так:

(f >>= g) x = g (f x) x 

это может быть полезно для создания функций, которые не могут быть выражены в виде конвейера. например, \x->x==nub xдлиннее nub>>=(==)и \t->zip(tail t)tдлиннее tail>>=zip.

гордый хаскеллер
источник
+1 - я даже не догадывался, что для функций был экземпляр монады. это может быть очень удобно :)
Жюль
2
Примечание: хотя это часть, Applicativeа не Monadее реализация, pureкоторая короче constи фактически помогла мне раньше.
ბიმო
14

Аргументы могут быть короче, чем определения

Я только что очень любопытным образом переиграл Хенкму .

Если вспомогательная функция fв вашем ответе использует оператор, который не используется в другом месте вашего ответа и fвызывается один раз, сделайте оператор аргументом f.

Этот:

(!)=take
f a=5!a++3!a
reverse.f

На два байта длиннее этого:

f(!)a=5!a++3!a
reverse.f take
Линн
источник
12

Используйте оператор "против" (:)

при объединении списков, если первый имеет длину 1, используйте :вместо него.

a++" "++b
a++' ':b  -- one character shorter

[3]++l
3:l    -- three characters shorter
гордый хаскеллер
источник
4
Стоит отметить, что это правильно ассоциативно, так что вы можете продолжать использовать его для любого количества отдельных элементов в начале списка, например, 1:2:3:xвместо [1,2,3]++x.
Жюль
11

Не используйте спины слишком часто. Обратные метки - отличный инструмент для создания разделов префиксных функций, но иногда ими можно злоупотреблять.

Однажды я увидел, как кто-то написал это подвыражение:

(x`v`)

Хотя это так же, как и просто v x.

Другой пример пишет (x+1)`div`y в отличие от div(x+1)y.

Я вижу, что это происходит вокруг divи elemчаще, потому что эти функции обычно используются как инфикс в обычном коде.

гордый хаскеллер
источник
Разве вы не имеете в виду создание разделов префиксных функций?
Cyoce
@Cyoce Да, конечно
гордый haskeller
11

Используйте шаблон охранников

Они короче, чем letлямбда или лямбда, которая деконструирует аргументы функции, которую вы определяете. Это помогает , когда вам нужно что - то вроде fromJustот Data.Maybe:

f x=let Just c=… in c

длиннее чем

f x=(\(Just c)->c)$…

длиннее чем

m(Just c)=c;f x=m$…

длиннее чем

f x|Just c<-…=c

На самом деле они короче даже при связывании простого старого значения вместо деконструкции: см . Совет xnor .

Линн
источник
Что ж, лямбда-единица не нуждается в знаке доллара, и, похоже, это изменение делает длины этого и следующего фрагмента одинаковыми
гордый haskeller
1
Я предполагаю, eчто на самом деле это не один токен, а более длинное выражение, которое нужно $перед ним, как это обычно бывает.
Линн
11

Короче условно

last$x:[y|b]

эквивалентно

if b then y else x

Вот как это работает:

             [y|b]   x:[y|b]   last$x:[y|b]
if...      +--------------------------------
b == False | []      [x]       x
b == True  | [y]     [x,y]     y
XNOR
источник
Должно ли это быть if b then y else x?
Akangka
@ChristianIrwan Хороший улов, да.
xnor
Не boolбудет ли короче, потому что вам не нужно понимание списка
Potato44
@ Potato44 Это в Data.Bool, который стоит байтов для импорта.
xnor
11

Работа со знаком минус

Знак минус -является раздражающим исключением из многих правил синтаксиса. Этот совет перечисляет некоторые короткие способы выражения отрицания и вычитания в 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+)работает как положено.
  • Чтобы вычесть 1, используйте predвместо этого, если это не потребует пробела с обеих сторон. Это редкое и обычно случается с untilили определяемой пользователем функции, так как map pred xможно заменить pred<$>xи iterate pred xна [x,x-1..]. И если у вас есть f pred xгде-то, вы fвсе равно должны определить как инфиксную функцию. Смотрите этот совет .
Zgarb
источник
11

Попробуйте переставить определения функций и / или аргументы

Иногда вы можете сохранить пару байтов, изменив порядок сопоставления с образцом в определении функции. Эти сбережения дешевы, но их легко не заметить.

В качестве примера рассмотрим следующую более раннюю версию (часть) этого ответа :

(g?x)[]=x
(g?x)(a:b)=g(g?x$b)a

Это рекурсивное определение ?, базовый случай - пустой список. Поскольку []это бесполезное значение, мы должны поменять определения и заменить их подстановочным знаком _или фиктивным аргументом y, сохранив байт:

(g?x)(a:b)=g(g?x$b)a
(g?x)y=x

Из того же ответа рассмотрим это определение:

f#[]=[]
f#(a:b)=f a:f#b

Пустой список встречается в возвращаемом значении, поэтому мы можем сохранить два байта, поменяв местами регистры:

f#(a:b)=f a:f#b
f#x=x

Кроме того, порядок аргументов функции может иногда иметь значение, позволяя вам удалить ненужные пробелы. Рассмотрим более раннюю версию этого ответа :

h p q a|a>z=0:h p(q+2)(a-1%q)|1<2=1:h(p+2)q(a+1%p)

Между первой hи pпервой ветвями есть досадный кусок пробела . Мы можем избавиться от этого, определив h a p qвместо h p q a:

h a p q|a>z=0:h(a-1%q)p(q+2)|1<2=1:h(a+1%p)(p+2)q
Zgarb
источник
10

Не тратьте "иначе" охрану

Последний защитный элемент, который является универсальным True(короче 1>0), может использоваться для привязки переменной. Для сравнения:

... |1>0=1/(x+y)
... |z<-x+y=1/z

... |1>0=sum l-sum m
... |s<-sum=s l-s m

Поскольку охрана является обязательной и в противном случае теряется, мало что нужно, чтобы это стоило того. Достаточно сохранить пару паренов или связать выражение длины 3, которое используется дважды. Иногда вы можете отменить охрану, чтобы в последнем случае было выражение, которое лучше всего использует привязку.

XNOR
источник
10

Используйте ,вместо &&охранников

Несколько условий в охране, которые все должны держать, могут быть объединены ,вместо &&.

f a b | a/=5 && b/=7 = ...
f a b | a/=5 ,  b/=7 = ...
Ними
источник
2
Это также не должно быть условий, вы можете делать такие вещи:f xs m | [x] <- xs, Just y <- m, x > 3 = y
BlackCap
10

Более короткий синтаксис для локальных объявлений

Иногда вам нужно определить локальную функцию или оператор, но это требует много байтов, чтобы написать whereили let…inили поднять его на верхний уровень, добавив дополнительные аргументы.

g~(a:b)=2!g b where k!l=k:take(a-1)l++(k+1)!drop(a-1)l
g~(a:b)=let k!l=k:take(a-1)l++(k+1)!drop(a-1)l in 2!g b
g~(a:b)=2!g b$a;(k!l)a=k:take(a-1)l++((k+1)!drop(a-1)l)a

К счастью, Haskell имеет запутанный и редко используемый, но достаточно краткий синтаксис для локальных объявлений :

fun1 pattern1 | let fun2 pattern2 = expr2 = expr1

В этом случае:

g~(a:b)|let k!l=k:take(a-1)l++(k+1)!drop(a-1)l=2!g b

Вы можете использовать этот синтаксис с объявлениями с несколькими операторами или несколькими объявлениями, и он даже вложен:

fun1 pattern1 | let fun2 pattern2 = expr2; fun2 pattern2' = expr2' = expr1
fun1 pattern1 | let fun2 pattern2 = expr2; fun3 pattern3 = expr3 = expr1
fun1 pattern1 | let fun2 pattern2 | let fun3 pattern3 = expr3 = expr2 = expr1

Это также работает для связывания переменных или других шаблонов, хотя защита шаблонов, как правило, короче для этого, если вы не являетесь также обязательными функциями.

Андерс Касеорг
источник
3
Это также работает в списковых: [f 1|let f x=x+1].
Лайкони
10

избежать repeat n

-- 8 bytes, whitespace might be needed before and after
repeat n

-- 8 bytes, whitespace might be needed before
cycle[n]

-- 7 bytes, whitespace might be needed before and after, can be reused,
-- needs an assignment, n needs to be global
l=n:l;l

-- 7 bytes, never needs whitespace, n needs to derive from Enum,
-- n has to be short enough to be repeated twice
[n,n..]

Любое из этих четырех выражений создаст бесконечный список nсимволов.

Это очень специфический совет, но он может сэкономить до 3 байтов!

totallyhuman
источник
4
Если nявляется глобальным, l=n:l;lимеет такую ​​же длину и работает для (некоторых) более длинных выражений. (
Орджан Йохансен
@ ØrjanJohansen Я включил его в пост. Спасибо!
полностью человек
10

Более короткие условия, когда один результат - пустой список

Когда вам нужно условное выражение, которое возвращает список Aили пустой список в []зависимости от некоторого условия C, тогда существуют некоторые более короткие альтернативы обычным условным конструкциям:

if(C)then(A)else[] -- the normal conditional
last$[]:[A|C]      -- the golfy all-round-conditional
concat[A|C]        -- shorter and works when surrounded by infix operator
id=<<[A|C]         -- even shorter but might conflict with other infix operators
[x|C,x<-A]         -- same length and no-conflict-guarantee™
[0|C]>>A           -- shortest way, but needs surrounding parenthesis more often than not

Примеры: 1 , 2

Laikoni
источник
Второй имеет Aи []переключился.
Орджан Йохансен
@ ØrjanJohansen Исправлено, спасибо!
Лайкони
1
Ага! Но *>имеет более высокую надежность, чем >>(все еще немного низко для комфорта.)
Орджан Йохансен
9

Правила разбора лямбды

Лямбда-выражению на самом деле не нужны круглые скобки вокруг него - оно просто жадно захватывает все, так что все это по-прежнему анализируется, например, до

  • закрывающая пара - (foo$ \x -> succ x)
  • в - let a = \x -> succ x in a 4
  • конец строки - main = getContents>>= \x -> head $ words x
  • и т.д..

встречается, и есть некоторые странные крайние случаи, когда это может сэкономить вам один или два байта. Я считаю, что \также может использоваться для определения операторов, поэтому при использовании этого вам понадобится пробел при написании лямбда-выражения непосредственно после оператора (как в третьем примере).

Вот пример, где использование лямбды было самой короткой вещью, которую я мог понять. Код в основном выглядит так:

a%f=...
f t=sortBy(% \c->...)['A'..'Z']
Flonk
источник
9

Заменить letна лямбду

Обычно это может сократить уединенное вспомогательное определение, которое по какой-либо причине не может быть связано с охраной или определено глобально. Например, заменить

let c=foo a in bar

на 3 байта короче

(\c->bar)$foo a

Для нескольких вспомогательных определений коэффициент усиления, вероятно, меньше, в зависимости от количества определений.

let{c=foo a;n=bar a}in baz
(\c n->baz)(foo a)$bar a

let{c=foo a;n=bar a;m=baz a}in qux
(\c n m->qux)(foo a)(bar a)$baz a

let{c=foo a;n=bar a;m=baz a;l=qux a}in quux
(\c n m l->quux)(foo a)(bar a)(baz a)$qux a

Если некоторые определения относятся к другим, еще сложнее сохранить байты следующим образом:

let{c=foo a;n=bar c}in baz
(\c->(\n->baz)$bar c)$foo a

Основное предостережение при этом заключается в том, что letвы можете определять полиморфные переменные, а лямбда-выражения нет, как отмечает @ChristianSievers. Например,

let f=length in(f["True"],f[True])

результаты (1,1), но

(\f->(f["True"],f[True]))length

дает ошибку типа.

Zgarb
источник
1
Это редко имеет значение, но «семантически эквивалентный» обещает слишком много. У нас есть полиморпик let, поэтому мы можем сделать let f=id in (f 0,f True). Если мы попытаемся переписать это с помощью лямбды, это не проверка типа.
Кристиан Сиверс
@ChristianSievers Это правда, спасибо за примечание. Я отредактировал его.
Згарб
8

Привязать с помощью охранников

При определении именованной функции вы можете связать выражение с переменной в Guard. Например,

f s|w<-words s=...

делает так же, как

f s=let w=words s in ...
f s=(\w->...)$words s

Используйте это, чтобы сэкономить на повторных выражениях. Когда выражение используется дважды, оно безубыточно даже на длине 6, хотя проблемы с пробелами и приоритетами могут изменить это.

(В этом примере, если исходная переменная sне используется, ее нужно сделать короче

g w=...
f=g.words

но это не так для связывания более сложных выражений.)

XNOR
источник
Разве это не дубликат / особый случай этого ответа ?
Линн
@Lynn Оглядываясь назад, это особый случай, но когда я прочитал этот ответ, этот Justпример заставил меня подумать, что для сопоставления с образцом нужно извлечь из контейнера, а не хранить в выражении.
xnor
8

Используйте (0<$)вместо lengthдля сравнения

При тестировании, если список aдлиннее списка b, обычно пишут

length a>length b

Однако замена каждого элемента обоих списков одним и тем же значением, например 0, а затем сравнение этих двух списков может быть короче:

(0<$a)>(0<$b)

Попробуйте онлайн!

Скобки необходимы , потому что <$и операторы сравнения ( ==, >, <=, ...) имеют одинаковый уровень приоритета 4, хотя в некоторых других случаях они не могут быть необходимы, экономя даже больше байт.

Laikoni
источник
8

Более короткие transpose

Для использования transposeфункции Data.Listдолжен быть импортирован. Если это единственная функция, требующая импорта, можно сохранить байт, используя следующее foldrопределение transpose:

import Data.List;transpose
e=[]:e;foldr(zipWith(:))e

Обратите внимание, что поведение идентично только для списка списков одинаковой длины.

Я успешно использовал это здесь .

Laikoni
источник
8

Получить суффиксы

Используйте scanr(:)[]для получения суффиксов списка:

λ scanr(:)[] "abc"
["abc","bc","c",""]

Это намного короче чем tailsпосле import Data.List. Вы можете делать префиксы с помощью scanr(\_->init)=<<id(найденный Орджаном Йохансеном).

λ  scanr(\_->init)=<<id $ "abc"
["","a","ab","abc"]

Это экономит байт

scanl(\s c->s++[c])[]
XNOR
источник
Возможно scanl(flip(:))[] "abc"= ["","a","ba","cba"]также стоит упомянуть - иногда префиксы в обратном направлении не имеют значения.
Линн
3
Орьян Йохансен нашел однобайтовую более короткую альтернативу для префиксов:scanr(\_->init)=<<id
Laikoni