Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Adding to a string map in OCaml

Tags:

ocaml

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.

like image 665
duanemat Avatar asked Oct 07 '10 03:10

duanemat


2 Answers

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.
like image 91
Sami Avatar answered Nov 16 '22 20:11

Sami


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.

like image 10
Gilles 'SO- stop being evil' Avatar answered Nov 16 '22 21:11

Gilles 'SO- stop being evil'