Searching for functions

OCaml newbie here - coming from Haskell land out of curiosity.

I’m interested how you guys find your way around stdlib/packages etc?

Example: I’m writing a script and I want to lookup an environment variable. I know there’s probably some function along the lines of get_env somewhere, so I’d like to know where it is and what type it has. In Haskell I’d do a hoogle search along the lines of https://www.stackage.org/lts-13.18/hoogle?q=getenv - what would be my process in OCaml?

I tried googling “get env var in Ocaml” - first hit is a link to stdlib, but I’m using base. It did at least give me the hint that Sys is a relevant namespace, so I go and look at the docs for Base.Sys (many clicks later - https://ocaml.janestreet.com/ocaml-core/latest/doc/base/Base/Sys/index.html) but getenv isn’t listed. But it is apparently there…

There must be a better way?

5 Likes

The Hoogle equivalent for OCaml is called odig: https://erratique.ch/software/odig . You can install it locally and have it generate documentation for all installed packages. However, generated documentation is not globally searchable (see last point). Besides that, there are a few other strategies:

  • Familiarize yourself with the standard library that ships with every OCaml distribution: https://caml.inria.fr/pub/docs/manual-ocaml/libref/ . This is the equivalent of Haskell’s base package. The Prelude equivalent module is called Pervasives. You will find the Sys module here, and getenv in there.
  • Keep http://opam.ocaml.org/packages/ handy for when you’re given a package name to look up. Package documentation is mostly not uploaded to a central location like Haddock. (But people have been talking about setting that up at docs.ocaml.org.) You’ll probably need to open up and search through .mli files once in a while.
  • The old-style ocamldoc documentation pages (like the standard library I linked above) have very handy pages indexing types, values, and modules. However, the newer odoc documentation pages which are becoming the de facto standard do not, as of yet. There are a couple of issues tracking this.
6 Likes

Thanks for this useful question! I am curious (if you feel comfortable sharing) your interest in approaching OCaml via Haskell. What do you feel are some of OCaml’s advantages for your use case?

Thanks for this! I’d like to see odig support some sort of search functionality, that would really help someone like myself get orientated quicker.

At the moment I don’t have a use case in mind - I’ve just heard good things about ocaml and I’m curious to explore more. The language certainly has some cool features I’d like to have in Haskell (module functors, polymorphic variants, labelled arguments…etc), and without the zoo of extensions and exotic type-level things it does feel a bit more “industrial”. But I’m not sure I can give up purity having seen how useful it is in Haskell…

I have been particularly impressed by the Bucklescript project - specifically the quality of the generated JS code - and could see myself using that on the frontend for non-trivial projects.

2 Likes

I’d like to add that at some point aswell, but don’t hold your breath for now.

A low effort improvement with great usability returns would be for odoc to at least generate per package index pages (like ocamldoc did), this issue tracks this.

1 Like

It would definitely be nice to have something like hoogle, with global function name and type signature search. Elm has something like that as well.

1 Like

Merlin has a search feature, :MerlinSearch in vim or M-x merlin-search in emacs.
The command takes a query which is a list of type names prefixed by - or +:

  • - for the type of values you have and the function you are looking for can consume
  • + for values you would like to get.

For instance, :MerlinSearch -int +string gives you ways to transform an integer to a string:

string_of_int                             V int -> string                   
Bytes.create                              V int -> bytes                    
BytesLabels.create                        V int -> bytes                    
String.create                             V int -> bytes                    
StringLabels.create                       V int -> bytes                    
Big_int.approx_big_int                    V int -> Big_int.big_int -> string
Num.approx_num_exp                        V int -> Num.num -> string        
Num.approx_num_fix                        V int -> Num.num -> string        
Ratio.approx_ratio_exp                    V int -> Ratio.ratio -> string    
Ratio.approx_ratio_fix                    V int -> Ratio.ratio -> string    
...

Parametric types are flattened (only variance is considered), so String.concat can be found with -string -list +string. I haven’t thought about filtering based on the name of values, but this should be easy to implement in a future version of merlin, e.g. env -string +string for finding string transformers that contains “env” in their name.

The UI has not seen much work and the search is blocking, which is not super convenient, help is welcome to improve that :).

18 Likes

That’s really cool. I’d like to play around with it on the command line, are there any docs? I looked at https://github.com/ocaml/merlin/blob/master/doc/dev/PROTOCOL.md and ran ocamlmerlin single -commands-help, but not seeing anything about a search functionality.

Never mind, I figured it out :slight_smile:

echo '\n' | ocamlmerlin single search-by-polarity -position 0 -query "-int +string" | less

Output is JSON, so would look better with some formatting:

echo '\n' | ocamlmerlin single search-by-polarity -position 0 -query "-int +string" | jq . | less
2 Likes

Here are two small bash/zsh functions building on @yawaramin’s work that might be useful. They will display a list of functions + the signatures. ocs will only display the first 10 because I don’t always want to fill my screen where as osca will list them all.

ocs () { echo '\n' | ocamlmerlin single search-by-polarity -position 0 -query $1 | jq '.value.entries[] | "\(.name) = \(.desc)"' | head -n 10 }
ocsa () { echo '\n' | ocamlmerlin single search-by-polarity -position 0 -query $1 | jq '.value.entries[] | "\(.name) = \(.desc)"' }
2 Likes

Fantastic! Minor quibble, but I’d use : instead of = to separate the name and type. Also another option for the output is to pipe to less.

1 Like

Does it search through all the opam-installed libraries?
Or just the currently opened project?
Why not support plain type signatures like int -> string, why do you need the - and +?
I will give it a try for sure.

Searching by type signatures would be super useful too.

Honestly, the OCaml community doesn’t have something that come close to Haskell’s hoogle.
Which is really annoying when you know the productivity boost hoogle allows…

Does it search through all the opam-installed libraries?
Or just the currently opened project?

It searches only in the project scope. This is a proof-of-concept, but it can easily be enhanced to do opam-wide search (I didn’t think about it because that I never felt the need).
Also, the search space can easily be indexed (while right now it is doing linear search), so performance should not be a problem at scale. I need to talk to @dbuenzli about that :).

Why not support plain type signatures like int → string, why do you need the - and +?

With the current spec, the search is directed by variance which more accurately reflects the flow of information than a mere ->. If it is just about syntax, of course the query language can be reworked, it is just frontend work (similarly, Hoogle does not strictly search by signature and do some approximation).

By relying only on variance, values are found even if they execute in a different monad, are written in a continuation passing-style or need more contexts (e.g. a context value).
Results can then be ranked by relevance (though that bit has subjective elements in it).

4 Likes

Even if the project looks asleep, it may be interesting to mention it here : https://github.com/camlspotter/ocamloscope.2.

That’s really neat, but it’s not clear to me how to search for generic types.

For example, M-x merlin-search RET -'a list +'a gives me an error:

"exception":"Not_found
Raised at file \"src/ocaml/typing/405/ident.ml\", line 168, characters 6-21
Called from file \"src/ocaml/typing/405/env.ml\", line 183, characters 22-43
Called from file \"src/ocaml/typing/405/env.ml\" (inlined), line 1057, characters 2-57
Called from file \"src/ocaml/typing/405/env.ml\", line 1127, characters 26-50
Called from file \"src/analysis/polarity_search.ml\", line 83, characters 29-52
Called from file \"src/utils/std.ml\", line 100, characters 12-15
Called from file \"src/utils/std.ml\", line 102, characters 23-39
Called from file \"src/analysis/polarity_search.ml\", line 86, characters 17-62
Called from file \"src/frontend/query_commands.ml\", line 403, characters 6-125
Called from file \"src/utils/local_store.ml\", line 29, characters 8-12
Re-raised at file \"src/utils/local_store.ml\", line 37, characters 4-15
Called from file \"src/kernel/mocaml.ml\", line 40, characters 8-38
Re-raised at file \"src/kernel/mocaml.ml\", line 48, characters 4-15
Called from file \"src/frontend/new/new_commands.ml\", line 64, characters 15-53
Called from file \"src/utils/std.ml\", line 654, characters 8-12
Re-raised at file \"src/utils/std.ml\", line 656, characters 30-39
Called from file \"src/ocaml/utils/misc.ml\", line 30, characters 20-27
Re-raised at file \"src/ocaml/utils/misc.ml\", line 30, characters 50-57
Called from file \"src/frontend/new/new_merlin.ml\", line 97, characters 18-54
"

Does the search syntax support this? I had a look at the merlin README and wiki, but didn’t see anything about this feature.

No, type parameters are ignored. Just write -list.

Aha, so it’s only concrete types? Could I find List.for_all and List.exists using this? In this case I have a function 'a -> bool and a 'a list and I was wondering if merlin search could handle this.