Tips umum apa yang Anda miliki untuk bermain golf di Haskell? Saya mencari ide yang dapat diterapkan pada masalah kode golf secara umum yang setidaknya agak spesifik untuk Haskell. Harap kirim hanya satu tip per jawaban.
Jika Anda baru bermain golf di Haskell, silakan lihat Panduan Aturan Golf di Haskell . Ada juga ruang obrolan Haskell khusus: Of Monads and Men .
Jawaban:
Tetapkan operator infiks alih-alih fungsi biner
Ini biasanya menghemat satu atau dua spasi per definisi atau panggilan.
vs.
Simbol tersedia untuk operator 1-byte yang
!
,#
,%
,&
, dan?
. Semua tanda baca ASCII lainnya sudah didefinisikan sebagai operator oleh Prelude (seperti$
) atau memiliki arti khusus dalam sintaksis Haskell (seperti@
).Jika Anda membutuhkan lebih dari lima operator, Anda bisa menggunakan kombinasi di atas, seperti
!#
, atau karakter tanda baca Unicode tertentu, seperti ini (semua 2 byte di UTF-8):sumber
(x!y)z=x+y*z
dan(x#y)z u=x*z+y*u
keduanya berfungsi seperti yang diharapkan.\f g(!)x y->f g!x y
alih-alih\f g j x y->j(f g)(x y)
g x=…;g(f x)
lebih lama dari_?x=…;0!f x
Gunakan notasi pointless (atau -free) jika sesuai
Seringkali suatu fungsi dengan satu atau dua parameter dapat ditulis bebas titik.
Jadi pencarian untuk daftar tupel yang elemen-elemennya ditukar secara naif ditulis sebagai:
(jenisnya ada hanya untuk membantu Anda memahami apa yang dilakukannya.)
untuk tujuan kita ini jauh lebih baik:
sumber
Gunakan daftar monad
Ulasan singkat:
Contoh:
Mengulangi daftar dua kali
Singkat
concatMap
concat
Pemahaman daftar + lebih pendekProduk Cartesian
Daftar koordinat pada kisi
sumber
[0..b]>>[a]
bukanreplicate a b
.a<$[1..b]
bahkan lebih pendek, untukreplicate
.=<<
memaksa Anda untuk mengimporControl.Monad
. Jika Anda tidak membutuhkannya karena alasan lain, bertukar argumen dan menggunakan>>=
tampaknya lebih ringkas.Data.Traversable
, contoh produk Cartesian dapat disingkat menjadifor["Hh","io",".!"]id
.(=<<)
ada di Prelude , sebenarnya! Saya sudah sering menggunakannya.Gunakan pelindung bukan kondisional:
Gunakan titik koma bukan indentasi
Gunakan ekspresi boolean untuk fungsi boolean
(SO menjadi sakit karena membiarkan saya memposting ini secara terpisah)
sumber
&&
saat berada dalam pemahaman daftar.True
=>1>0
f a=if a>0 then 3 else 7
interact :: (String → String) → IO ()
Orang sering lupa bahwa fungsi ini ada - ia mengambil semua stdin dan menerapkannya pada fungsi (murni). Saya sering melihat
main
-kode di sepanjang barissementara
sedikit lebih pendek. Itu ada di Pendahuluan jadi tidak perlu impor!
sumber
Gunakan GHC 7.10
Versi pertama GHC yang berisi hal-hal ini dirilis pada 27 Maret 2015 .
Ini versi terbaru, dan Prelude mendapatkan beberapa tambahan baru yang berguna untuk bermain golf:
The
(<$>)
dan(<*>)
operatorOperator yang bermanfaat ini
Data.Applicative
berhasil masuk!<$>
hanyafmap
, sehingga Anda dapat menggantimap f x
danfmap f x
dengan dif<$>x
mana saja dan memenangkan kembali byte. Juga,<*>
berguna dalamApplicative
contoh untuk daftar:The
(<$)
Operatorx<$a
setara denganfmap (const x) a
; yaitu mengganti setiap elemen dalam wadah denganx
.Ini sering merupakan alternatif yang bagus untuk
replicate
:4<$[1..n]
lebih pendek darireplicate n 4
.Proposal Lipat / Dapat Dilewati
Fungsi-fungsi berikut diangkat dari mengerjakan daftar
[a]
keFoldable
tipe umumt a
:Ini berarti mereka sekarang juga bekerja
Maybe a
, di mana mereka berperilaku seperti "daftar dengan paling banyak satu elemen". Misalnyanull Nothing == True
,, atausum (Just 3) == 3
. Demikian pula,length
mengembalikan 0 untukNothing
dan 1 untukJust
nilai. Alih-alih menulis,x==Just y
Anda dapat menuliselem y x
.Anda juga dapat menerapkannya pada tupel, yang berfungsi seolah-olah Anda sudah menelepon
\(a, b) -> [b]
dulu. Ini hampir sama sekali tidak berguna, tetapior :: (a, Bool) -> Bool
satu karakter lebih pendek darisnd
, danelem b
lebih pendek dari(==b).snd
.Fungsi Monoid
mempty
danmappend
Tidak sering penyelamat, tetapi jika Anda dapat menyimpulkan jenisnya,
mempty
satu byte lebih pendek dari ituNothing
, jadi itulah.sumber
<*>
menjadikannya sebagai Prelude! Itu harus berguna bahkan ketika itu bukan kode golf (aplikatif adalah kata yang panjang).[1..2]
di sana. itu hanya[1,2]
<*
dariApplicative
, yang untuk daftar adalahxs <* ys == concatMap (replicate (length ys)) xs
. Ini berbeda darixs >> ys
atauxs *> ys
yang adaconcat (replicate (length ys)) xs
.pure
yang lebih pendekreturn
datang pada saat ini juga.<>
sebagai gantimappend
, itu sekarang (dengan GHC 8.4.1) bagian dariPrelude
.Gunakan
1<2
sebagai gantiTrue
dan1>2
bukanFalse
.sumber
f=max 10
.if(true)
dalam bahasa lain. di awal, sebaliknya sebenarnya adalah nilai booleanTrue
.otherwise
.Gunakan pemahaman daftar (dengan cara pintar)
Semua orang tahu itu sintaks yang berguna, seringkali lebih pendek dari
map
+ a lambda:Atau
filter
(dan secara opsional amap
pada saat yang sama):Tetapi ada beberapa kegunaan aneh yang berguna sekarang dan nanti. Pertama, pemahaman daftar tidak perlu mengandung
<-
panah sama sekali:Yang berarti alih-alih
if p then[x]else[]
, Anda bisa menulis[x|p]
. Juga, untuk menghitung jumlah elemen daftar yang memenuhi syarat, secara intuitif Anda akan menulis:Tapi ini lebih pendek:
sumber
Ketahui milik Anda
Prelude
Jalankan GHCi dan gulir melalui dokumentasi Prelude . Setiap kali Anda melewati suatu fungsi yang memiliki nama pendek, itu bisa bermanfaat untuk mencari beberapa kasus di mana itu mungkin berguna.
Misalnya, Anda ingin mengubah string
s = "abc\ndef\nghi"
menjadi string yang dipisahkan oleh ruang"abc def ghi"
,. Cara yang jelas adalah:Tetapi Anda bisa berbuat lebih baik jika Anda menyalahgunakan
max
, dan fakta bahwa\n < space < printable ASCII
:Contoh lain adalah
lex :: String -> [(String, String)]
, yang melakukan sesuatu yang sangat misterius:Cobalah
fst=<<lex s
untuk mendapatkan token pertama dari sebuah string, melompati spasi putih. Berikut adalah solusi cerdas oleh henkma yang menggunakanlex.show
padaRational
nilai-nilai.sumber
Cocokkan dengan nilai konstan
Pemahaman daftar dapat mencocokkan pola pada konstanta.
Ini mengekstrak 0 dari daftar
l
, yaitu membuat daftar sebanyak 0 dalaml
.Ini membuat daftar
1
sebanyak yang ada elemenl
yangf
dibutuhkan untuk daftar kosong (menggunakan<$>
sebagai infixmap
). Terapkansum
untuk menghitung elemen-elemen ini.Membandingkan:
Konstanta dapat digunakan sebagai bagian dari kecocokan pola. Ini mengekstrak entri kedua dari semua tupel yang entri pertamanya adalah
0
.Perhatikan bahwa semua ini membutuhkan konstanta literal aktual, bukan nilai variabel. Misalnya,
let x=1 in [1|x<-[1,2,3]]
akan menampilkan[1,1,1]
, bukan[1]
, karenax
pengikatan luar ditimpa.sumber
Gunakan
words
sebagai ganti daftar panjang string. Ini tidak terlalu spesifik untuk Haskell, bahasa lain juga memiliki trik yang serupa.sumber
Ketahui fungsi monadik Anda
1)
mensimulasikan fungsi monadik menggunakan
mapM
.banyak kali kode akan miliki
sequence(map f xs)
, tetapi dapat diganti denganmapM f xs
. bahkan ketika hanya menggunakansequence
itu saja sudah lebih lamamapM id
.2)
menggabungkan fungsi menggunakan
(>>=)
(atau(=<<)
)versi fungsi monad
(>>=)
didefinisikan sebagai:ini bisa berguna untuk membuat fungsi yang tidak bisa diekspresikan sebagai pipeline. misalnya,
\x->x==nub x
lebih panjang darinub>>=(==)
, dan\t->zip(tail t)t
lebih panjang daritail>>=zip
.sumber
Applicative
dan tidakMonad
ada implementasinyapure
juga yang lebih pendek dariconst
dan benar-benar membantu saya sebelumnya.Argumen bisa lebih pendek dari definisi
Saya baru saja dikalahkan dengan cara yang sangat aneh oleh henkma .
Jika fungsi bantu
f
dalam jawaban Anda menggunakan operator yang tidak digunakan di tempat lain dalam jawaban Anda, danf
dipanggil sekali, buat operator argumenf
.Ini:
Apakah dua byte lebih panjang dari ini:
sumber
Gunakan operator kontra (:)
saat menggabungkan daftar, jika yang pertama adalah panjang 1 kemudian gunakan
:
sebagai gantinya.sumber
1:2:3:x
Alih-alih[1,2,3]++x
.Jangan gunakan backticks terlalu sering. Backticks adalah alat keren untuk membuat bagian fungsi awalan, tetapi kadang-kadang dapat disalahgunakan.
Pernah saya melihat seseorang menulis subekspresi ini:
Meskipun sama dengan adil
v x
.Contoh lain adalah menulis
(x+1)`div`y
sebagai lawandiv(x+1)y
.Saya melihatnya terjadi sekitar
div
danelem
lebih sering karena fungsi-fungsi ini biasanya digunakan sebagai infix dalam kode reguler.sumber
Gunakan pelindung pola
Mereka lebih pendek dari a
let
atau lambda yang mendekonstruksi argumen fungsi yang Anda definisikan. Ini membantu ketika Anda membutuhkan sesuatu sepertifromJust
dariData.Maybe
:lebih panjang dari
lebih panjang dari
lebih panjang dari
Bahkan, mereka lebih pendek bahkan ketika mengikat nilai lama polos alih-alih mendekonstruksi: lihat tip xnor .
sumber
e
sebenarnya bukan satu tanda tetapi ekspresi yang lebih lama yang diperlukan$
sebelum itu, yang biasanya terjadi.Bersyarat lebih pendek
setara dengan
Begini cara kerjanya:
sumber
if b then y else x
?bool
lebih pendek karena Anda tidak memerlukan pemahaman daftarBekerja dengan tanda minus
Tanda minus
-
adalah pengecualian yang menjengkelkan bagi banyak aturan sintaksis. Kiat ini mencantumkan beberapa cara singkat untuk mengekspresikan negasi dan pengurangan di Haskell. Tolong beri tahu saya jika saya melewatkan sesuatu.Penyangkalan
e
, lakukan saja-e
. Misalnya,-length[1,2]
memberi-2
.e
bahkan cukup kompleks, Anda akan membutuhkan tanda kurunge
, tetapi Anda biasanya dapat menyimpan byte dengan memindahkannya:-length(take 3 x)
lebih pendek dari-(length$take 3 x)
.e
didahului oleh=
atau operator infiks dari fixity kurang dari 6, Anda memerlukan ruang:f= -2
mendefinisikanf
dank< -2
menguji jikak
kurang dari-2
. Jika fixity 6 atau lebih besar, Anda perlu parens:2^^(-2)
memberi0.25
. Anda biasanya dapat mengatur ulang barang untuk menyingkirkan ini: misalnya, lakukan-k>2
alih - alihk< -2
.!
adalah operator, maka-a!b
diurai seolah-(-a)!b
olah fixity!
paling banyak 6 (jadi-1<1
memberiTrue
), dan-(a!b)
sebaliknya (jadi-[1,2]!!0
memberi-1
). Memperbaiki standar operator yang ditentukan pengguna dan fungsi backticked adalah 9, sehingga mereka mengikuti aturan kedua.map
dll), gunakan bagian ini(0-)
.Pengurangan
k
, gunakan bagian(-k+)
yang menambahkan-k
.k
bahkan bisa menjadi ekspresi yang cukup rumit:(-2*length x+)
berfungsi seperti yang diharapkan.pred
sebaliknya, kecuali jika itu membutuhkan ruang di kedua sisi. Ini jarang terjadi dan biasanya terjadi denganuntil
atau fungsi yang ditentukan pengguna, karenamap pred x
dapat digantikan olehpred<$>x
daniterate pred x
oleh[x,x-1..]
. Dan jika Anda memilikif pred x
suatu tempat, Anda mungkin harus mendefinisikanf
sebagai fungsi infiks. Lihat tip ini .sumber
Coba atur ulang definisi fungsi dan / atau argumen
Anda kadang-kadang dapat menyimpan beberapa byte dengan mengubah urutan kasus pencocokan pola dalam definisi fungsi. Tabungan ini murah, tetapi mudah untuk dilewatkan.
Sebagai contoh, pertimbangkan versi sebelumnya dari (bagian dari) jawaban ini :
Ini adalah definisi rekursif
?
, dengan kasus dasar menjadi daftar kosong. Karena[]
bukan nilai yang berguna, kita harus menukar definisi dan menggantinya dengan wildcard_
atau argumen dummyy
, menyimpan byte:Dari jawaban yang sama, pertimbangkan definisi ini:
Daftar kosong terjadi pada nilai kembali, sehingga kami dapat menyimpan dua byte dengan menukar kasus:
Juga, urutan argumen fungsi terkadang dapat membuat perbedaan dengan memungkinkan Anda untuk menghapus spasi yang tidak perlu. Pertimbangkan versi sebelumnya dari jawaban ini :
Ada ruang putih yang mengganggu antara
h
danp
di cabang pertama. Kita dapat menyingkirkannya dengan mendefinisikanh a p q
alih-alihh p q a
:sumber
Jangan sia-siakan penjaga "jika tidak"
Pelindung terakhir yang merupakan catch-all
True
(lebih pendek1>0
) dapat digunakan untuk mengikat variabel. Membandingkan:Karena penjaga itu wajib dan jika tidak disia-siakan, sedikit yang diperlukan untuk membuat ini sepadan. Ini cukup untuk menyimpan sepasang paren atau mengikat ekspresi panjang-3 yang digunakan dua kali. Terkadang Anda bisa meniadakan penjaga untuk menjadikan kasus terakhir sebagai ekspresi yang paling baik menggunakan penjilidan.
sumber
Gunakan
,
sebagai ganti&&
penjagaBerbagai kondisi dalam penjaga yang semua harus pegang dapat dikombinasikan dengan
,
bukan&&
.sumber
f xs m | [x] <- xs, Just y <- m, x > 3 = y
Sintaks yang lebih pendek untuk deklarasi lokal
Kadang-kadang Anda perlu mendefinisikan fungsi atau operator lokal, tetapi membutuhkan banyak byte untuk menulis
where
ataulet…in
mengangkatnya ke tingkat atas dengan menambahkan argumen tambahan.Untungnya, Haskell memiliki sintaks yang membingungkan dan jarang digunakan tetapi cukup singkat untuk deklarasi lokal :
Pada kasus ini:
Anda dapat menggunakan sintaks ini dengan deklarasi multi-pernyataan atau beberapa deklarasi, dan bahkan bersarang:
Ini juga berfungsi untuk variabel yang mengikat atau pola lain, meskipun penjaga pola cenderung lebih pendek untuk itu kecuali Anda juga fungsi yang mengikat.
sumber
[f 1|let f x=x+1]
.Menghindari
repeat n
Salah satu dari empat ekspresi itu akan menghasilkan daftar tak terhingga
n
.Ini tip yang sangat spesifik tetapi bisa menghemat hingga 3 byte!
sumber
n
bersifat global,l=n:l;l
panjangnya sama dan berfungsi untuk (beberapa) ekspresi yang lebih panjang. (Mungkin perlu spasi putih.)Persyaratan lebih pendek ketika satu hasil adalah daftar kosong
Ketika Anda membutuhkan kondisional yang mengembalikan daftar
A
atau daftar kosong[]
tergantung pada beberapa kondisiC
, maka ada beberapa alternatif yang lebih pendek untuk konstruksi kondisional yang biasa:Contoh: 1 , 2
sumber
A
dan[]
beralih.*>
memiliki fixity yang lebih tinggi daripada>>
(masih agak rendah untuk kenyamanan.)Aturan penguraian Lambda
Ekspresi lambda sebenarnya tidak membutuhkan tanda kurung di sekitarnya - itu hanya dengan rakus mengambil semuanya sehingga semuanya masih terurai, misalnya sampai
(foo$ \x -> succ x)
let a = \x -> succ x in a 4
main = getContents>>= \x -> head $ words x
ditemui, dan ada beberapa kasus tepi aneh di mana ini dapat menghemat satu atau dua byte. Saya percaya
\
juga dapat digunakan untuk mendefinisikan operator, jadi ketika mengeksploitasi ini Anda akan memerlukan ruang ketika menulis lambda langsung setelah operator (seperti pada contoh ketiga).Berikut adalah contoh di mana menggunakan lambda adalah hal tersingkat yang dapat saya pikirkan. Kode ini pada dasarnya terlihat seperti:
sumber
Ganti
let
oleh lambdaIni biasanya dapat mempersingkat definisi pembantu tunggal yang tidak dapat diikat dengan penjaga atau didefinisikan secara global karena beberapa alasan. Misalnya, ganti
oleh 3 byte lebih pendek
Untuk beberapa definisi tambahan, keuntungan mungkin lebih kecil, tergantung pada jumlah definisi.
Jika beberapa definisi merujuk ke yang lain, bahkan lebih sulit untuk menyimpan byte dengan cara ini:
Peringatan utama dengan ini adalah yang
let
memungkinkan Anda untuk mendefinisikan variabel polimorfik, tetapi lambdas tidak, seperti dicatat oleh @ChristianSievers. Sebagai contoh,menghasilkan
(1,1)
, tetapimemberikan kesalahan ketik.
sumber
let
, jadi kami bisa melakukannyalet f=id in (f 0,f True)
. Jika kami mencoba untuk menulis ulang ini dengan lambda itu tidak ketik cek.Ikat menggunakan penjaga
Saat mendefinisikan fungsi bernama, Anda bisa mengikat ekspresi ke variabel di penjaga. Sebagai contoh,
melakukan hal yang sama dengan
Gunakan ini untuk menghemat ekspresi berulang. Ketika ekspresi digunakan dua kali, itu memecah bahkan pada panjang 6, meskipun masalah spasi dan prioritas dapat mengubah itu.
(Dalam contoh, jika variabel asli
s
tidak digunakan, ini lebih pendek untuk dilakukantapi itu tidak benar untuk mengikat ekspresi yang lebih kompleks.)
sumber
Just
contohnya membuat saya berpikir itu adalah pencocokan pola untuk mengekstrak dari sebuah wadah, daripada untuk menyimpan pada ekspresi.Gunakan
(0<$)
sebagai gantilength
untuk perbandinganSaat menguji apakah daftar
a
lebih panjang dari daftarb
, biasanya orang akan menulisNamun mengganti setiap elemen dari kedua daftar dengan nilai yang sama, misalnya
0
, dan kemudian membandingkan kedua daftar itu bisa lebih pendek:Cobalah online!
Kurung diperlukan karena
<$
dan operator perbandingan (==
,>
,<=
, ...) memiliki hak tingkat yang sama 4, meskipun dalam beberapa kasus lain mereka mungkin tidak diperlukan, menghemat bahkan lebih byte.sumber
Singkat
transpose
Untuk menggunakan
transpose
fungsiData.List
harus diimpor. Jika ini adalah satu-satunya fungsi yang membutuhkan impor, seseorang dapat menyimpan byte menggunakanfoldr
definisi berikuttranspose
:Perhatikan bahwa perilaku ini hanya identik untuk daftar daftar dengan panjang yang sama.
Saya berhasil menggunakan ini di sini .
sumber
Dapatkan sufiks
Gunakan
scanr(:)[]
untuk mendapatkan sufiks dari daftar:Ini jauh lebih pendek daripada
tails
sesudahnyaimport Data.List
. Anda dapat melakukan awalan denganscanr(\_->init)=<<id
(ditemukan oleh Ørjan Johansen).Ini menghemat lebih dari satu byte
sumber
scanl(flip(:))[] "abc"
=["","a","ba","cba"]
juga layak disebutkan - terkadang awalan yang mundur tidak masalah.scanr(\_->init)=<<id