tags:

views:

109

answers:

2

Hi,

I have been trying to figure out what I figure is a pretty simple task, namely adding entries to a map of Strings in OCaml from within a function. The relevant elements are below:

module StringMap = Map.Make (String);;
let m = StringMap.empty;;

let rec count ke = match ke with
 |[] -> []
 |hd::tl -> begin let m = StringMap.add hd 1 m; [hd] @ count tl end;;

I keep receiving the cryptic "Syntax Error" message, but I have yet to find a solution even after stripping the code down to basically nothing. I can add to the String Map with a single command, but if I try to run let m = StringMap.add hd 1 m from within function, it fails to run. I am sure this is a simple problem, but can someone please help? Thanks.

+5  A: 

There seem to be a few problems with your code. But first, here is an example on how to do what you are trying to do:

module StringMap = Map.Make (String)

let rec count m ke =
    match ke with
    | [] -> m
    | hd :: tl -> count (StringMap.add hd 1 m) tl

let m = count StringMap.empty ["foo"; "bar"; "baz"]

Some comments on the original code:

  • double semicolons are used to tell the REPL loop that you want it to consume your code. You should avoid them if not using it.
  • single semicolons are used to separate imperative expressions, e.g. following an assignment. Here you have a let expression, which has syntax "let <..> in <..>", so your use of a semicolon after it is wrong. ("let a = ..." without "in" is a toplevel construct, and not something you can use locally).
  • StringMap (and most other structures in ocaml) are functional, not imperative. You can't mutate the 'm' that you declared in the first line. You have to build the structure, and finally return the fully built structure in the end of the loop.
Sami
Good answer (though I think it deserves [more explanation](http://stackoverflow.com/questions/3878527/adding-to-a-string-map-in-ocaml/3879553#3879553)), except that I wouldn't recommend not to use double semicolons. They're not needed, but they do no harm either.
Gilles
There is some discussion of the meaning of ;; here: http://old.nabble.com/%22ocaml_beginners%22%3A%3A---meaning-of----in-source-code-td29701184.html
Gaius
+5  A: 

Your source of confusion is that you've misunderstood the meaning of the let construct. It does not modify an object: it's not an assignment statement (. The let construct gives a name to a value. The syntax of the let construct is
    let name = value in expression
This causes name to refer to value in expression. The value is computed once, before binding the name to it. The scope of name is expression, so you can't use it to refer to value outside expression (the value, on the other hand, lives on until garbage collected).

The top-level let construct is similar to the one in expressions, but does not have the in expression part. The scope of the name is the rest of the program (it's as though there was an in part containing everything below, at least until you get to modules).

You're trying to modify the top-level m, but that is not let's job: you would need an assignment for that. Ocaml has an assignment operator, :=, which assigns to an existing reference. A reference is an object that can be modified. This is not automatic in Ocaml: unlike languages such as C, Java and Lisp, Ocaml does not use a single language feature to give names to values and to create modifiable storage. The ref function creates a modifiable object; you assign it with := and use the ! operator to get its value:

let r_m = ref StringMap.empty;; (*the type of r_m is 'a StringMap.t ref*)
let rec count ke = match ke with
  | [] -> []
  | hd::tl -> r_m := StringMap.add hd 1 !r_m; [hd] @ count tl;;

This is however not very good Ocaml style. Ocaml supports this imperative style, but you should avoid it because imperative programming is more error-prone than functional programming. The functional style is to create a new value whenever you want to modify an object. Note that maps created by the Map module are designed to support this style: add returns a new object which coexists with the old object (i.e. it's a persistent data structure). Switching to a functional style requires that you change the interface of the count function; it's something that you'd want to do anyway, to be able to use the count function on different maps by passing the map as an argument. I refer you to Sami's answer for example code in good Ocaml style.

Gilles