## Part 1: Variables and scope In lecture you learned that at the top level, let x = 0;; let y = 1;; is really shorthand for let x = 0 in let y = 1 in <everything else that follows> This is one of many examples where indentation or notation changes how you think about a piece of code. Even though the two are equivalent, the first *seems* more like a sequence of definitions, and thinking of it this way makes it easier to read about. It is also easier to look at the code and see where each variable is defined. For this reason, we **do not** indent the body of the let (the part after the in). However, if we need to break a definition (the part after the = but before the in) across multiple lines, we **do** indent those lines:  let x = if true then 1 else 0 in let y = x + x in x + x   let f x = let helper x y = let t = x + y in let z = t * t + x + y in z * z in helper 3 x;; f 17  This last snippet is an example of a very common pattern: by keeping the helper function helper within the scope of f, we clearly tell the reader that helper is only needed for f. Variables defined in small scopes are preferable to variables defined in large scopes because they allow the programmer to ignore them most of the time. For these exercises, do not use the top level (except to check your work). Start by breaking these expressions on to multiple lines and adding indentataion to help you reason about them. **Exercise:** Give the values of the following expressions: 1. let x = 0 in let x = 1 in x 2. let x = let x = 0 in x in x 3. let x = 0 in let f x = x in let x = 1 in f 2 4. let x = 0 in let f y = x in let x = 1 in f 2 5. let x = let y = 0 in y in let z = 1 in let w = 2 in x + y + z + w **Exercise:** Do expressions 3 and 4 differ only in the names of variables. Does this violate the Principle of Name Irrelevance? Why or why not? ## Part 2: Modules As we saw above, limiting the scope of variables is good practice because it allows programmers to ignore variables that aren't relevant. Like let ... in expressions, modules are mechanism for limiting the scope of names. ### Defining modules A module is a collection of related variables with values. Modules are created by placing a series of definitions in between struct ... end, and they are given names using the module keyword. All modules have names that start with uppercase letters.  module MyModule = struct let x = 3 let f y = x + y end  You can access the values within the module by prefacing them with the name of the module and a .. You've already seen examples of this with the Random module:  utop# Random.int;; - : int -> int = <fun>  **Exercise:** Add MyModule to your working file for this recitation and #use it. Write a function (outside of the module) that uses the values in the MyModule module. Just like values, modules have types (although they are called module types instead of just types). Module types are bracketed by sig ... end and list all of the definitions inside the module. We will discuss module types in much more detail later. For now, the useful thing to know about them is that you can print the type of a module in the toplevel:  utop# module type T = module type of Random;;  **Exercise:** What is the module type of MyModule? **Exercise:** Using just utop, find the string that is used to separate file names. It's in the Filesomething module, but you don't remember exactly and your internet connection is down. ### Opening modules As you've seen, you can access the contents of modules using a .. You can also bring all of the definitions of a module into the current scope using open:  utop# capitalize;; Error: Unbound value capitalize utop# String.capitalize;; - : string -> string = <fun> utop# open String;; utop# capitalize;; - : string -> string = <fun>  Opening a module is just like writing a let statement for each variable defined in the module. open String does the same thing as  let length = String.length let get = String.get let set = String.set ... let capitalize = String.capitalize ...  Occasionally, you may want to open a module in a limited scope (such as in the body of a function). The syntax for this is let open M in .... ### Pervasives There is a special module called Pervasives that is automatically opened. It contains the "built-in" functions and operators. **Exercise:** In the toplevel, look at the list of variables in the Pervasives module. Find the name of the function that prints out a string followed by an end of line character. ### A note on style You now know three ways to access a variable from a module. Directly:  String.capitalize  By making a short name for the module:  module S = String ... S.capitalize  Or by opening the module:  open String ... capitalize  **Exercise:** What are the advantages of each style? When would you use which? ### Compiling and loading modules Compiling an OCaml file produces a module having the same name as the file (but with the first letter capitalized). We use the cs3110 tool to compile and test your assignments. These compiled modules can be loaded into the toplevel using #load. **Exercise:** Create a file called mods.ml. Inside that file, define a variable x of type int, two functions f and g, and a module called M containing a variable y. At the command line, type cs3110 compile mods.ml to compile it. Before you can load a module compiled with the cs3110 tool, you have to set up utop properly: - You must specify a directory to load compiled files from using #directory. cs3110 compile outputs compiled modules in the _build directory, so you need to type #directory "_build";; - You must require any third-party libraries. Modules compiled with cs3110 compile depend on the pa_ounit library. You need to type #require "pa_ounit";; **Exercise:** At the toplevel, type #load "mods.d.cmo";; to load the file you just compiled. As with everything else you type into utop, you will want to put these #require, #directory and #load commands into another file that you will #use from utop (to save yourself typing). Notice that x and y are not defined (as they would be if you had #used mods.ml). However, there is a module Mods available; Mods.x, Mods.f and Mods.g are defined. **Exercise:** Access y from the top level. **Exercise:** Use a single open statement in utop to bring y into scope. When compiling a file f.ml, cs3110 compile automatically figures out which other files f.ml depends on, and recompiles those as necessary. **Exercise:** Create a file mods2.ml. In it, create a function f that depends on Mods.x. Use cs3110 compile to compile it. Notice that only mods2.ml was compiled. Now, modify mods.ml, and recompile mods2.ml. Notice that both mods.ml and mods2.ml were recompiled. **Exercise:** To see clearly the difference between #load and #use, start a new toplevel, #use mods.ml, and then #use mods2.ml. Why doesn't this work? Unfortunately, utop's #load isn't as smart as cs3110 compile: it does not automatically load dependencies. **Exercise:** Start a fresh toplevel. #load "mods2.d.cmo" (don't forget #require and #directory). Notice the error message telling you that Mods is undefined. Load mods and then reload mods2. While you're using the toplevel to experiment with your code, it's better to compile and use #load, because this accurately reflects how your modules interact with each other and with the outside world (including our testing suite). As you've already discovered, it can be a pain to get the #load statements in the right order. As mentioned in lab 2, it's a good practice to have a file that you #use while you are working in utop. The #load statements for the modules you are testing along with the #require and #directory commands are good things to put in there. ## Part 3: Printing OCaml has a number of ways to print things. Pervasives has print_ functions for all of the built-in types: print_int, print_string, print_char, and so on. There is also the convenient print_endline function, which is like print_string, but it breaks the line after printing. The Printf.printf function is a very versatile and useful function for printing. In the last lab, you used it to print octal and hexadecimal numbers, but it can be used for much more. The first argument to printf is a "format string": it looks like a normal string, but it can contain placeholders that are replaced by the remaining arguments to printf. For example, the format string "hello %s (version %d) \n" has two placeholders: the "%s" indicates that the next argument to printf should be a string, and the "%d" indicates that the argument after that should be an integer, and will be printed in decimal.  utop# Printf.printf "hello %s (version %d) \n";; - : string -> int -> unit = <fun> utop# Printf.printf "hello %s (version %d) \n" "world" 1;; hello world (version 1) - : unit = ()  See the documentation for the Printf module for full explanations of the available placeholders. **Exercise:** Fill in the format string in the following expression:  utop# Printf.printf "TODO" 95.0 true 5.0 false "ocaml";;  so that the output is  95% true 5% false 100% ocaml! - : unit = ()  This can be done with only three spaces in the format string. Curiously, the type of printf depends on the format string that is provided. This is possible only because the OCaml language has special support for format strings. ### Unit The return type of all of the printing functions is a type called unit. There is only one value of this type, which is written () and is also pronounced "unit". Unit is used when you need to take an argument or return a value, but there's no interesting value to pass or return. Unit is often used when you're writing or using code that has side-effects. Since you usually want to ignore values of type unit, there is a special syntax for doing so. The expression e1; e2 first evaluates e1, which should produce (). It then discards that value, and evaluates e2. Here is how ; is often used:  let f _ = let x = 3 + 4 in print_string "x = "; print_int x; print_newline (); let y = x + x in Printf.printf "y = %i\n" y; Printf.printf "returning %i" (y + y + x); y + y + x  If e1 does not have type unit, then e1;e2 will give a warning, because you are discarding useful values. If that is truly your intent, you can call the built-in function ignore to convert any value to ():  utop# 3; 5;; Characters 0-1: Warning 10: this expression should have type unit. - : int = 5 utop# ignore 3; 5;; - : int = 5  **Exercise:** Modify your recursive rev_base function from the last lab so that it prints something on every recursive call. ### Welcome to the dark side Printing is your first taste of a "side effect": you don't call printing functions for the values they return, but for the other things they do while they run. Side effects break many of the promises we've made about the benefits of functional programming. **Exercise:** To fully appreciate the power of the dark side, predict the output of the following functions:  let lightning_storm _ = let f x y = print_endline "A"; x + y in (print_endline "B"; f) (print_endline "C"; 0) (print_endline "D"; 1)   let corrupt_padowan _ = let f x y = print_endline "A"; x + y in let g = print_endline "B"; f in g (print_endline "C"; 0) (print_endline "D"; 1)  Verify your answer. Feel the power of the dark side flow through you.