Поради щодо гольфу в Хаскеллі


73

Які загальні поради щодо гольфу в Хаскеллі? Я шукаю ідеї, які взагалі можуть бути застосовані для кодування проблем із гольфом, які принаймні дещо характерні для Haskell. Будь ласка, опублікуйте лише одну пораду за кожну відповідь.


Якщо ви новачок у гольфі в Хаскеллі, ознайомтеся з Посібником з правил гольфу в Хаскеллі . Існує також спеціальна кімната для чатів Haskell: Monads and Men .


1
Побачивши кількість відповідей до цього часу, я сумніваюся, чи є Haskell навіть хорошою мовою для кодування в гольф чи ні?
Анімеш 'КОДЕР'

10
Чому лише одна порада на відповідь? Також кожна мова є гарною мовою для гри в гольф. Просто не завжди розраховуйте на перемогу.
нечисте

6
@unclemeat Таким чином люди могли підняти добрих до верху, не домагаючись поганих лише тому, що їх написав той самий хлопець у тій самій відповіді.
MasterMastic

3
Спеціальний запит, Стиснення струни.
J Atkin

Це, мабуть, не підходить як відповідь, але я все одно хочу його додати сюди: wiki.haskell.org/Prime_numbers_miscellaneous#One-liners
flawr

Відповіді:


45

Визначте оператори інфікування замість бінарних функцій

Це зазвичай економить одне або два пробіли на визначення чи виклик.

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

vs.

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

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

Якщо вам потрібно більше п'яти операторів, ви можете використовувати комбінації вищезазначених, наприклад !#, або певні знаки пунктуації Unicode, такі як ці (усі 2 байти в UTF-8):

¡ ¢ £ ¤ ¥ ¦ § ¨ © ¬ ® ¯ ° ± ´ ¶ · ¸ ¿ × ÷

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

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

ОТОХ, якщо вам 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

(ТАК болить за те, щоб дозволити мені публікувати це окремо)


2
також використовуйте декілька охоронців замість того, &&коли всередині списку розуміння.
Джон Дворак

Добрий січень - ти повинен це зробити у відповідь, я проголосую за це
bazzargh

5
Перший приклад можна додатково скоротити на True=>1>0
Джон Дворак

1
у першому прикладі я припускаю, що ви маєте на увазіf a=if a>0 then 3 else 7
Cyoce

Guard навіть працює, якщо в ньому немає аргументів.
Акангка

24

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

Люди часто забувають, що ця функція існує - вона захоплює весь stdin і застосовує її до (чистої) функції. Я часто бачу main-код по рядках

main=getContents>>=print.foo

поки

main=interact$show.foo

зовсім трохи коротше. Це в прелюдії, тому немає потреби в імпорті!


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.

Функції Monoid memptyіmappend

Не часто рятує життя, але якщо ви можете зробити висновок про тип, memptyце на один байт коротше Nothing, так ось це.


5
+1 Приємно чути про "<$>` та <*>перетворення його на прелюдію! Це повинно бути корисним навіть у тому випадку, коли це не код гольфу (додаток - це таке довге слово).
ankh-morpork

Попередження про рівну заміну: якщо ваша мовна версія є новішою, ніж виклик, ваше рішення не помітно для перемоги. Якщо ви хочете оновити наявні відповіді чи відповіді, не перезаписуйте існуючі рішення. Напишіть додаток.
Джон Дворак

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прийшов і в цей момент.
Ангс

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

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 та прокрутіть документацію Прелюдії . Кожен раз, коли ви перетинаєте функцію, яка має коротке ім’я, вона може окупитися, щоб знайти деякі випадки, коли це може бути корисним.

Наприклад, припустимо , що ви хочете , щоб перетворити рядок 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в'язання перезаписано.


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

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 - Я навіть не зрозумів, що існує монада для функцій. це може бути дуже зручно :)
Jules

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

Чи має бути if b then y else x?
Акангка

@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все одно повинні визначатись як функція інфікування. Дивіться цю пораду .

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

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, який використовується двічі. Іноді можна заперечувати охоронців, щоб остаточний випадок став виразом, який найкраще використовує прив'язку.


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].
Laikoni

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..]

Будь-який із цих чотирьох виразів створить нескінченний список ns.

Це дуже конкретна порада, але вона може заощадити до 3 байт!


4
Якщо nглобальний, l=n:l;lмає однакову довжину і працює для (деяких) довших виразів. (Може знадобитися прогалини , хоча.)
Ørjan Йохансен

@ Ø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


Другий має Aі []перемикається.
Ørjan Johansen

@ ØrjanJohansen Виправлено, дякую!
Лайконі

1
Ага! Але він *>має більш високу чіткість, ніж >>(все ще трохи низький для комфорту.)
Ørjan Johansen

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']

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

дає помилку типу.


1
Це рідко має значення, але "семантично рівнозначний" обіцяє трохи забагато. У нас є поліморпічні let, тому ми можемо це зробити let f=id in (f 0,f True). Якщо ми спробуємо переписати це за допомогою лямбда, він не перевіряє.
Крістіан Сіверс

@ChristianSievers Це правда, дякую за замітку. Я відредагував це.
Згарб

8

Пов'язують за допомогою варт

Визначаючи іменовану функцію, ви можете прив'язати вираз до змінної у захисті. Наприклад,

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

робить те саме, що і

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

Використовуйте це, щоб заощадити на повторних виразах. Якщо вираз використовується два рази, він розбивається навіть на довжину 6, хоча проблеми з інтервалом та пріоритетом можуть змінити це.

(У прикладі, якщо початкова змінна sне використовується, це зробити коротше

g w=...
f=g.words

але це неправда для зв'язування складніших виразів.)


Хіба це не дублікат / особливий випадок цієї відповіді ?
Лінн

@Lynn Озираючись назад, це особливий випадок, але коли я прочитав цю відповідь, Justприклад змусив мене думати, що це для відповідності шаблону для вилучення з контейнера, а не для зберігання на виразі.
xnor

8

Використовуйте (0<$)замість lengthпорівняння

Під час тестування, якщо список aдовший, ніж список b, зазвичай писали б

length a>length b

Однак заміна кожного елемента обох списків однаковим значенням, наприклад 0, і порівняння цих двох списків може бути коротшим:

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

Спробуйте в Інтернеті!

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


8

Коротше transpose

Для використання transposeфункції Data.Listпотрібно імпортувати. Якщо це єдина функція, яка потребує імпорту, можна зберегти байт, використовуючи таке foldrвизначення transpose:

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

Зауважте, що поведінка однакова лише для списків списків однакової довжини.

Я успішно використовував це тут .


8

Доберіть суфікси

Використовуйте scanr(:)[]для отримання суфіксів списку:

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

Це набагато коротше, ніж tailsпісля import Data.List. Ви можете робити префікси за допомогою scanr(\_->init)=<<id(знайдений Ørjan Johansen).

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

Це економить байт

scanl(\s c->s++[c])[]

Можливо scanl(flip(:))[] "abc"= ["","a","ba","cba"]варто також згадати - іноді префікси назад не мають значення.
Лінн

3
Ørjan Johansen знайшов однобайтову коротку альтернативу для префіксів:scanr(\_->init)=<<id
Laikoni
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.