Hoogle este un motor de căutare pentru funcții, tipuri, clase etc. de Haskell. Puterea sa constă în abilitatea de a căuta funcții atât după nume cât și după tip.

Căutare după nume

Considerați că tocmai ați auzit de funcția Haskell scanl, dar nu știți ce face. Căutând numele pe Hoogle (https://www.haskell.org/hoogle/?hoogle=scanl) obțineți o listă de rezultate similare; observați că hoogle face matching parțial și vă găsește și funcții numite scanl1.

Este foarte posibil ca pentru o singură funcție, să găsiți mai multe rezultate cu același nume. În general, acestea sunt funcții din pachete diferite cu același comportament dar specializate pentru alte tipuri de date. De obicei, primul rezultat ar trebui să fie cel dorit.

Anatomia unui rezultat

Fiecare rezultat are următoarele componente:

  • pe prima linie este numele complet al funcției, urmat de tipul funcției
    • click pe numele sau pe tipul funcției vă va duce la descrierea completă a acesteia din cadrul pachetului în care e definită
  • următoarea linie (cu verde) conține o listă separată cu virgulă de forma “pachet modul-din-pachet” în care se găsește funcția (nu e necesar să înțelegeți bine noțiunile de “pachet” și de “modul”; cel mai des vă veți uita după funcții care conțin base Prelude)
  • următoarea linie conține o descriere a comportamentului funcției; butonul + de la începutul rândului expandează acest câmp, dacă descrierea este lungă

Citind descrierea funcției scanl, observăm că aceasta operează ca foldl, dar întoarce o listă cu toate rezultatele parțiale; astfel încât primul element al listei este acumulatorul, iar ultimul rezultatul final:

Prelude> foldl (+) 0 [14, 28, 2]
44
Prelude> scanl (+) 0 [14, 28, 2]
[0,14,42,44]
Prelude>
întâi 0, acumulatorul
apoi 14, rezultatul operației (0 + 14)
apoi 42, rezultatul operației (14 + 28)
apoi 44, rezultatul operației (42 + 2)

Căutare după tip

Când scrieți cod (indiferent de limbaj), veți ajunge des în situația în care aveți nevoie de un anumit comportament; vreți să găsiți o funcție despre care știți ce ați vrea să facă, însă nu știți cum s-ar putea numi. Ar fi util să existe o implementare pentru această funcție în biblioteca standard; astfel vă scutiți de timpul de dezvoltare a funcției și aveți garanția unei implementări robuste, testată de mulți. Hoogle vă ajută să o găsiți, dacă există.

Exemplu

Aveți de rezolvat următoarea problemă: se dă o listă de numere întregi; trebuie să o rearanjați astfel încât toate numerele pare să se afle la începutul listei, urmate de toate numerele impare.

[1, 2, 3, 4] -> [2, 4, 1, 3]
[44, 2, 13, 8, 77] -> [44, 2, 8, 13, 77]
[11, 1, 3, 9, 2] -> [2, 11, 1, 3, 9]

Pentru acest task, cel mai util ar fi să existe o funcție care face exact asta; din păcate aceasta nu se găsește în biblioteca standard Haskell. Gândidu-ne la ce ne-ar fi de folos, putem ajunge la ideea că ne dorim o funcție care primește un predicat și o listă de întregi și ne întoarce un tuplu de două liste: elementele pentru care predicatul ține și restul. Funcția ar avea semnătura:

(Int -> Bool) -> [Int] -> ([Int], [Int])

Căutând acest tip pe hoogle, găsim 3 rezultate!
https://www.haskell.org/hoogle/?hoogle=%28Int+-%3E+Bool%29+-%3E+%5BInt%5D+-%3E+%28%5BInt%5D%2C+%5BInt%5D%29

Observăm că rezultatele nu au exact tipul dat de noi. Tipul la care ne-am gândit a fost mai restrictiv decât necesar, biblioteca standard oferindu-ne o soluție pentru liste de orice tip generic a; hoogle a fost însă în stare să le găsească.

Dar ordinea parametrilor? Ce s-ar fi întâmplat dacă am fi căutat o funcție care primește întâi o listă, apoi predicatul?
https://www.haskell.org/hoogle/?hoogle=%5BInt%5D+-%3E+%28Int+-%3E+Bool%29+-%3E+%28%5BInt%5D%2C+%5BInt%5D%29

Aceleași rezultate! Hoogle reușește să găsească ce ne dorim chiar și în ciuda ordinii greșite a parametrilor.

Acum nu ne mai rămâne decât să citim descrierile funcțiilor găsite (break, span, partition). Observăm că partition are comportamentul dorit. Hoogle ne arată faptul că această funcție se află în modulul Data.List, care trebuie importat:

Prelude> :m + Data.List
Prelude Data.List> partition even [1..10]
([2,4,6,8,10],[1,3,5,7,9])
Prelude Data.List>

Concatenând cele două liste, obținem soluția problemei inițiale:

import Data.List
 
evensFirst l = let (e, o) = partition even l
               in e ++ o
Am putea scrie o expresie mai scurtă, folosindu-ne de compunere de funcții? Funcția partition întoarce un tuplu, dar funcția de concatenare (++) primește pe rând două argumente - este curried. Putem rezolva problema, folosindu-ne de uncurry.
import Data.List
 
evensFirst = uncurry (++) . partition even

Funcția uncurry (++) are tipul ([a], [a]) -> [a].