Cs51 introduction to objective caml
This presentation is the property of its rightful owner.
Sponsored Links
1 / 114

CS51: Introduction to Objective Caml PowerPoint PPT Presentation


  • 79 Views
  • Uploaded on
  • Presentation posted in: General

CS51: Introduction to Objective Caml. Greg Morrisett. Ocaml is a functional language. Small, orthogonal core based on the lambda-calculus. Control is based on (recursive) functions. Instead of for-loops, while-loops, do-loops, iterators , etc.

Download Presentation

CS51: Introduction to Objective Caml

An Image/Link below is provided (as is) to download presentation

Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author.While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server.


- - - - - - - - - - - - - - - - - - - - - - - - - - E N D - - - - - - - - - - - - - - - - - - - - - - - - - -

Presentation Transcript


Cs51 introduction to objective caml

CS51: Introduction to Objective Caml

Greg Morrisett


Ocaml is a functional language

Ocaml is a functional language

  • Small, orthogonal core based on the lambda-calculus.

    • Control is based on (recursive) functions.

    • Instead of for-loops, while-loops, do-loops, iterators, etc.

      • rather, we can define these as library (functions).

    • Makes it easy to define semantics (and learn!)

  • Supports higher-order, lexically-scoped, first-class procedures.

    • a.k.a. closures or lambdas.

    • first-class: can be passed to other procedures as arguments, returned from procedures as results, placed in data structures etc.

    • lexically-scoped: meaning of variables determined statically.

    • higher-order: compositional, programs that build programs.

      These aspects are in common with other functional languages such as Scheme, Haskell, SML, Clojure, CoffeeScript.


  • Ocaml is mostly pure

    Ocaml is mostly pure.

    • As opposed to mostly imperative languages.

      • C/C++, Java, Python, Basic, Fortran, …

    • Emphasis on values, not variables or state:

      • “value-oriented programming”

      • build new values out of old, instead of destructively updating the old.

      • leads to a simple analytic, computational model

        • in particular, you can reason locally about pure code.

        • you can test locally and be assured environment doesn’t break test.

        • important for scale – even in languages like Java, you should try to avoid imperative updates unless you need them (see J.Bloch’s book).

        • increasingly important in age of concurrency

    • Still provides for mutable data structures and I/O.

      • needed to obtain asymptotic efficiency for some algorithms (e.g., unification.)

      • but less frequently than you might first suspect.


    Ocaml is call by value

    Ocaml is call-by-value

    • Most languages today are call-by-value.

      • arguments to procedures are evaluated to values.

      • then the values are passed to the procedure

      • e.g., f (3+4, 5+3) f (7, 8)

    • Some languages are call-by-name.

      • arguments are re-computed each time they are used.

      • pure lambda calculus, Algol, the C pre-processor, TeX

    • Some languages are call-by-need (a.k.a. lazy)

      • arguments are computed once, but only on demand.

      • Haskell, Unix pipes

      • We will see some uses of laziness later in the class --- it’s a useful pattern to understand and utilize.


    Laziness can be a virtue

    Laziness can be a virtue…


    Xkcd on laziness

    XKCD on Laziness


    Ocaml is strongly statically typed

    Ocaml is strongly, statically typed

    • Statically typed:

      • compiler catches many silly errors before you can run the code.

      • e.g., calling a function with the wrong number of arguments

      • Java is also strongly, statically typed.

      • Scheme, Bash, Python, Javascript, Basic, etc. are all strongly, dynamically typed – type errors are discovered while the code is running.

    • Strong typed: compiler enforces type abstraction.

      • cannot cast an integer to a record, function, string, etc.

        • so we can utilize types as capabilities.

        • crucial for local reasoning at the level of the language.

      • C/C++ are weakly-typed languages. The compiler will happily let you do something smart (more often stupid).


    Static cat

    Static Cat


    A bit of history

    A bit of history

    Alonzo Church: lambda calculus1930’s

    Guy Steele & Gerry Sussman:

    Scheme

    late 1970’s

    Xavier Leroy:Ocaml1990’s

    Robin Milner, MadsTofte, & Robert Harper

    Standard ML

    1980’s

    John McCarthy:

    LISP

    1958

    Don Syme:F#2000’s


    A bit of fun

    A bit of fun:

    http://www.malevole.com/mv/misc/killerquiz/


    Installing running ocaml

    Installing, running Ocaml

    • We run Ocaml inside a virtual machine appliance.

      • same as in CS50.

      • instructions for installing part of first problem set.

      • helps to provide uniform environment.

      • you can bypass this and install Ocaml directly, but we don’t want to have to help you debug your setup, so recommend using the appliance.

    • Ocaml comes with an interactive, top-level loop.

      • useful for testing and debugging code.

      • “ocaml” at the prompt.

    • It also comes with compilers

      • “ocamlc” – fast bytecode compiler

      • “ocamlopt” – optimizing, native code compiler

      • command line interface similar to GCC

    • And many other tools

      • e.g., debugger, dependency generator, profiler, etc.


    Editing ocaml programs

    Editing Ocaml Programs

    • Many options: pick your own poison

      • Emacs + Tuareg mode

        • what I’ll be using in class.

        • good but not great support for Ocaml.

        • on the other hand, it’s still the best code editor I’ve encountered.

        • (extensions written in elisp – a functional language!)

      • vim, Gedit, etc.

        • barebones support.

      • Visual Studio

        • syntax highlighting and so forth for F#.

        • warning: we will be sticking to Ocaml, so I don’t recommend this.

      • Eclipse

        • I’ve put up a link to an Ocaml plugin (but haven’t tried it).


    Xkcd on editors

    XKCD on Editors


    An example

    An Example:

    (* find "name" "path": tries to find all occurrences of files with the

    given name, starting at the given path, recursively exploring all sub-

    directories. Returns a list of the full paths to the files.

    *)

    let recfind name path =

    try

    let fullname = Filename.concat path name in

    if Sys.file_existsfullnamethen

    [ fullname ]

    else if Sys.is_directory path then

    let files = Array.to_list (Sys.readdir path) in

    let paths = List.map (Filename.concat path) files in

    let results = List.map (find name) paths in

    List.flatten results

    else []

    with Sys_error _ -> []

    ;;


    An example1

    An Example:

    Comments start with “(*” and end with “*)”.

    (* find "name" "path": tries to find all occurrences of files with the

    given name, starting at the given path, recursively exploring all sub-

    directories. Returns a list of the full paths to the files.

    *)

    let recfind name path =

    try

    let fullname = Filename.concat path name in

    if Sys.file_existsfullnamethen

    [ fullname ]

    else if Sys.is_directory path then

    let files = Array.to_list (Sys.readdir path) in

    let paths = List.map (Filename.concat path) files in

    let results = List.map (find name) paths in

    List.flatten results

    else []

    with Sys_error _ -> []

    ;;


    An example2

    An Example:

    Top-level definitions start with “let” and if they are recursive “rec”

    Top-level definitions end with a double-semicolon “;;”

    (* find "name" "path": tries to find all occurrences of files with the

    given name, starting at the given path, recursively exploring all sub-

    directories. Returns a list of the full paths to the files.

    *)

    let recfind name path =

    try

    let fullname = Filename.concat path name in

    if Sys.file_existsfullnamethen

    [ fullname ]

    else if Sys.is_directory path then

    let files = Array.to_list (Sys.readdir path) in

    let paths = List.map (Filename.concat path) files in

    let results = List.map (find name) paths in

    List.flatten results

    else []

    with Sys_error _ -> []

    ;;


    An example3

    An Example:

    try … with Sys_error _ -> e

    tries to execute the “…”

    If the Sys_error exception is thrown, we jump to here and

    immediately return an empty list [ ].

    (* find "name" "path": tries to find all occurrences of files with the

    given name, starting at the given path, recursively exploring all sub-

    directories. Returns a list of the full paths to the files.

    *)

    let recfind name path =

    try

    let fullname = Filename.concat path name in

    if Sys.file_existsfullnamethen

    [ fullname ]

    else if Sys.is_directory path then

    let files = Array.to_list (Sys.readdir path) in

    let paths = List.map (Filename.concat path) files in

    let results = List.map (find name) paths in

    List.flatten results

    else []

    with Sys_error _ -> []

    ;;


    An example4

    An Example:

    We declare local variables using “let x = … in …”

    (* find "name" "path": tries to find all occurrences of files with the

    given name, starting at the given path, recursively exploring all sub-

    directories. Returns a list of the full paths to the files.

    *)

    let recfind name path =

    try

    let fullname = Filename.concat path name in

    if Sys.file_existsfullnamethen

    [ fullname ]

    else if Sys.is_directory path then

    let files = Array.to_list (Sys.readdir path) in

    let paths = List.map (Filename.concat path) files in

    let results = List.map (find name) paths in

    List.flatten results

    else []

    with Sys_error _ -> []

    ;;


    An example5

    An Example:

    Here we are calling the “concat” function which is in the Filename module.

    I found it by looking in the Ocaml Reference Manual.

    (* find "name" "path": tries to find all occurrences of files with the

    given name, starting at the given path, recursively exploring all sub-

    directories. Returns a list of the full paths to the files.

    *)

    let recfind name path =

    try

    let fullname = Filename.concat path name in

    if Sys.file_existsfullnamethen

    [ fullname ]

    else if Sys.is_directory path then

    let files = Array.to_list (Sys.readdir path) in

    let paths = List.map (Filename.concat path) files in

    let results = List.map (find name) paths in

    List.flatten results

    else []

    with Sys_error _ -> []

    ;;


    An example6

    An Example:

    The Sys.file_exists function tells us if the file exists. If so, we return a list containing just that file’s full name.

    (* find "name" "path": tries to find all occurrences of files with the

    given name, starting at the given path, recursively exploring all sub-

    directories. Returns a list of the full paths to the files.

    *)

    let recfind name path =

    try

    let fullname = Filename.concat path name in

    if Sys.file_existsfullnamethen

    [ fullname ]

    else if Sys.is_directory path then

    let files = Array.to_list (Sys.readdir path) in

    let paths = List.map (Filename.concat path) files in

    let results = List.map (find name) paths in

    List.flatten results

    else []

    with Sys_error _ -> []

    ;;


    An example7

    An Example:

    If path points to a directory, we want to process all of the files in that directory, including all of the nested directories. (What isn’t quite right here?)

    (* find "name" "path": tries to find all occurrences of files with the

    given name, starting at the given path, recursively exploring all sub-

    directories. Returns a list of the full paths to the files.

    *)

    let recfind name path =

    try

    let fullname = Filename.concat path name in

    if Sys.file_existsfullnamethen

    [ fullname ]

    else if Sys.is_directory path then

    let files = Array.to_list (Sys.readdir path) in

    let paths = List.map (Filename.concat path) files in

    let results = List.map (find name) paths in

    List.flatten results

    else []

    with Sys_error _ -> []

    ;;


    An example8

    An Example:

    We start by getting an array of file names in the directory “path” using the Sys.readdir function.

    (* find "name" "path": tries to find all occurrences of files with the

    given name, starting at the given path, recursively exploring all sub-

    directories. Returns a list of the full paths to the files.

    *)

    let recfind name path =

    try

    let fullname = Filename.concat path name in

    if Sys.file_existsfullnamethen

    [ fullname ]

    else if Sys.is_directory path then

    let files = Array.to_list (Sys.readdir path) in

    let paths = List.map (Filename.concat path) files in

    let results = List.map (find name) paths in

    List.flatten results

    else []

    with Sys_error _ -> []

    ;;


    An example9

    An Example:

    And then convert the array to a list.

    (* find "name" "path": tries to find all occurrences of files with the

    given name, starting at the given path, recursively exploring all sub-

    directories. Returns a list of the full paths to the files.

    *)

    let recfind name path =

    try

    let fullname = Filename.concat path name in

    if Sys.file_existsfullnamethen

    [ fullname ]

    else if Sys.is_directory path then

    let files = Array.to_list (Sys.readdir path) in

    let paths = List.map (Filename.concat path) files in

    let results = List.map (find name) paths in

    List.flatten results

    else []

    with Sys_error _ -> []

    ;;


    An example10

    An Example:

    Notice that (Filename.concat path) is only partially applied. It’s List.map’s job to supply the next argument, which it does for each file name in the list.

    Here, we are iterating over the list of files, concatenating the path to each file name, and getting a new list of paths.

    (* find "name" "path": tries to find all occurrences of files with the

    given name, starting at the given path, recursively exploring all sub-

    directories. Returns a list of the full paths to the files.

    *)

    let recfind name path =

    try

    let fullname = Filename.concat path name in

    if Sys.file_existsfullnamethen

    [ fullname ]

    else if Sys.is_directory path then

    let files = Array.to_list (Sys.readdir path) in

    let paths = List.map (Filename.concat path) files in

    let results = List.map (find name) paths in

    List.flatten results

    else []

    with Sys_error _ -> []

    ;;


    An example11

    An Example:

    This iterates over all the paths, calling (find name) on them recursively, which means we get back a list of lists of full paths.

    (* find "name" "path": tries to find all occurrences of files with the

    given name, starting at the given path, recursively exploring all sub-

    directories. Returns a list of the full paths to the files.

    *)

    let recfind name path =

    try

    let fullname = Filename.concat path name in

    if Sys.file_existsfullnamethen

    [ fullname ]

    else if Sys.is_directory path then

    let files = Array.to_list (Sys.readdir path) in

    let paths = List.map (Filename.concat path) files in

    let results = List.map (find name) paths in

    List.flatten results

    else []

    with Sys_error _ -> []

    ;;


    An example12

    An Example:

    Finally, we convert the list of lists of full paths into a single list of full paths by calling List.flatten.

    (* find "name" "path": tries to find all occurrences of files with the

    given name, starting at the given path, recursively exploring all sub-

    directories. Returns a list of the full paths to the files.

    *)

    let recfind name path =

    try

    let fullname = Filename.concat path name in

    if Sys.file_existsfullnamethen

    [ fullname ]

    else if Sys.is_directory path then

    let files = Array.to_list (Sys.readdir path) in

    let paths = List.map (Filename.concat path) files in

    let results = List.map (find name) paths in

    List.flatten results

    else []

    with Sys_error _ -> []

    ;;


    Primitive types values operations

    Primitive types, values, operations

    Type:Values:Example Expressions:

    int-2, 0, 4242 * (13 + 1)

    float3.14, -1., 2e12(3.14 +. 12.0) *. 10e6

    char‘a’, ’b’, ’&’int_of_char ‘a’

    string“moo”, “cow”“moo” ^ “cow”

    booltrue, falseif isduck then 3 else 4

    unit()()

    For more primitive types and functions over them, see the Ocaml Reference Manual here:

    http://caml.inria.fr/pub/docs/manual-ocaml/libref/Pervasives.html


    Expressions

    Expressions

    exp ::= valuenumbers, strings, bools, etc.

    | id variables (x, foo, etc.)

    | exp1 op exp2 x+3 or path ^ “/usr”

    | id exp1 exp2 … expnfunction call (foo 3 42)

    | let id = exp1in exp2 local variable decl.

    | if exp1then exp2else exp3a conditional

    Note: this is the abstract syntax; it doesn’t reflect the precedence of operators or the grouping you can do with parentheses (issues of concrete syntax.)


    Expressions1

    Expressions

    This is an example of Backus Naur Form (BNF) notation which is often used to describe the structure of a programming language.

    exp ::= valuenumbers, strings, bools, etc.

    | id variables (x, foo, etc.)

    | exp1 op exp2 x+3 or path ^ “/usr”

    | id exp1 exp2 … expnfunction call (foo 3 42)

    | let id = exp1in exp2 local variable decl.

    | if exp1then exp2else exp3a conditional

    Note: this is the abstract syntax; it doesn’t reflect the precedence of operators or the grouping you can do with parentheses (issues of concrete syntax.)


    Expressions2

    Expressions

    Here, we are describing what are valid Ocaml expressions as a recursive definition.

    exp ::= valuenumbers, strings, bools, etc.

    | id variables (x, foo, etc.)

    | exp1 op exp2 x+3 or path ^ “/usr”

    | id exp1 exp2 … expnfunction call (foo 3 42)

    | let id = exp1in exp2 local variable decl.

    | if exp1then exp2else exp3a conditional

    Note: this is the abstract syntax; it doesn’t reflect the precedence of operators or the grouping you can do with parentheses (issues of concrete syntax.)


    Expressions3

    Expressions

    The different alternatives are separated by a vertical bar (“|”).

    So an expression is either (1) a value, or (2) an expression, operator, and another expression, or (3) an identifier followed by a list of expressions (separated by spaces), or…

    exp ::= valuenumbers, strings, bools, etc.

    | id variables (x, foo, etc.)

    | exp1 op exp2 x+3 or path ^ “/usr”

    | id exp1 exp2 … expnfunction call (foo 3 42)

    | let id = exp1in exp2 local variable decl.

    | if exp1then exp2else exp3a conditional

    Note: this is the abstract syntax; it doesn’t reflect the precedence of operators or the grouping you can do with parentheses (issues of concrete syntax.)


    Expressions4

    Expressions

    It’s just a very succinct way to write down a definition for an infinite family of things.

    As we’ll see, ML provides a way to do this for data structures that makes it very easy to manipulate “language-like” things.

    exp ::= valuenumbers, strings, bools, etc.

    | id variables (x, foo, etc.)

    | exp1 op exp2 x+3 or path ^ “/usr”

    | id exp1 exp2 … expnfunction call (foo 3 42)

    | let id = exp1in exp2 local variable decl.

    | if exp1then exp2else exp3a conditional

    Note: this is the abstract syntax; it doesn’t reflect the precedence of operators or the grouping you can do with parentheses (issues of concrete syntax.)


    A note on parentheses

    A note on parentheses

    In most languages, one writes parentheses around comma-separate arguments:

    f(x,y,z) sum(3,4,5)

    In Ocaml, we don’t bother with parentheses or the commas: f x y z sum 3 4 5

    But we do have to worry about grouping. For example, these two expressions mean something different:

    f x y zf x (y z)

    The first one passes three arguments to f (x, y, and z) whereas the second passes two arguments to f (x, and the result of applying the function y to z.)

    When in doubt group things with parentheses.


    More ocaml compound types

    More Ocaml: compound types

    • Tuples (anonymous records, cartesian products):

      • (exp1, exp2, …, expn) (1,true,”greg”)

      • has type t1*t2*…*tnint*bool*string

      • To eliminate: let (id1,id2,…,idn) = e1 in e2

      • For example:

        let p = (1,3) in

        let (x,y) = pin x+y 4


    Better example

    Better Example:

    let distance p1 p2 =

    let (x1,y1) = p1 in

    let (x2,y2) = p2 in

    sqrt (square (x2 –. x1) +. (square (y2 –. y1))

    distance (3.0,5.0) (4.0,7.0) 

    2.23606797749979


    More ocaml options

    More Ocaml: Options

    • A value v is a T option if v is either Some(v’) where v’ is a value of type T, or else v is None.

    • Options are useful for signaling there’s no useful answer.

    • For example, we might write:

      let slope p1 p2 =

      let (x1,y1) = p1 in

      let (x2,y2) = p2 in

      if x2 –. x1 = 0.0 then None

      else Some((y2 –. y1) /. (x2 -. x1))

    • As another example, we might call lookup in a hash-table and return None when there is no value associated with the key, and Some(v) when v is associated with the key.


    Working with options

    Working with options

    • How do we use an option?

      • it could be None or it could be Some(v)

      • furthermore, we want to get at the value v when it’s Some(v).

    • We must use a match expression to handle both possible cases.

      match lookup table key with

      | None -> Printf.printf “key not found”

      | Some v -> Printf.printf “value is %d\n” v


    Let vs match

    Let vs. Match

    You can also tear apart a tuple using match:

    let distance p1 p2 =

    match p1 with

    | (x1,y1) ->

    match p2 with

    | (x2,y2) -> sqrt (square (x2 –. x1) +. (square (y2 –. y1))

    There’s only one case for tuples, which is why we can use “let” in place of this.


    An alternative

    An alternative:

    Matching can nest:

    let distance p1 p2 =

    match (p1,p2) with

    | ((x1,y1),(x2,y2)) ->

    sqrt (square (x2 –. x1) +. (square (y2 –. y1))

    We are matching (p1,p2) --- a pair of pairs of floats:(float * float) * (float * float)


    Lists

    Lists:

    • Lists

      • []the empty list

      • exp1::exp2add exp1 to the front of list exp2

        • The list of numbers from 0 to 3: 0::1::2::3::[]

        • Parses as 0::(1::(2::(3::[])))

        • We pronounce “::” as “cons”.

        • We say exp1 is the head of the list, and exp2 is the tail of the list.

        • Occasionally we will slip and say “car” for head, and “cdr” for tail.

      • [exp1;exp2;…;expn] shorthand forexp1::(exp2::(…::expn::[])…)

        [“Greg”;”Tanya”;”Amy”;”John”] ;;

        “Greg”::”Tanya”::”Amy”::”John”::[]


    Tearing apart a list

    Tearing apart a list

    Just as with options, there are two cases for lists: it could be empty ([ ]) or it could be a cons (e1 :: e2).

    let head x =

    match xwith

    | [] -> None

    | hd::tl -> Some hd ;;


    Tearing apart a list1

    Tearing apart a list

    The underscore (“_”) is a pattern that matches anything without giving it a name.

    Just as with options, there are two cases for lists: it could be empty ([ ]) or it could be a cons (e1 :: e2).

    let head x =

    match xwith

    | [] -> None

    | hd::_ -> Some hd ;;


    A more interesting example

    A more interesting example

    • Given a list of pairs of integers, produce the list of products of the pairs.

      • e.g., Given [(2,3); (4,7); (5,2)] return [6; 28; 10]


    A more interesting example1

    A more interesting example

    Start by writing the

    types of the function.

    • Given a list of pairs of integers, produce the list of products of the pairs.

      • e.g., Given [(2,3); (4,7); (5,2)] return [6; 28; 10]


    A more interesting example2

    A more interesting example

    Tear apart the input.

    • Given a list of pairs of integers, produce the list of products of the pairs.

      • e.g., Given [(2,3); (4,7); (5,2)] return [6; 28; 10]

        let recprods (l : (int*int) list) : int list =


    A more interesting example3

    A more interesting example

    The pair of ints (x,y) is the head of the list, and tl is the tail of the list.

    • Given a list of pairs of integers, produce the list of products of the pairs.

      • e.g., Given [(2,3); (4,7); (5,2)] return [6; 28; 10]

        let recprods (l : (int*int) list) : int list =

        match lwith

        | [] ->

        | (x,y)::tl ->


    A more interesting example4

    A more interesting example

    Solve the first case (easy).

    • Given a list of pairs of integers, produce the list of products of the pairs.

      • e.g., Given [(2,3); (4,7); (5,2)] return [6; 28; 10]

        let recprods (l : (int*int) list) : int list =

        match lwith

        | [] ->

        | (x,y)::tl ->


    A more interesting example5

    A more interesting example

    Solve the second case.

    Pay attention to the return type.

    • Given a list of pairs of integers, produce the list of products of the pairs.

      • e.g., Given [(2,3); (4,7); (5,2)] return [6; 28; 10]

        let recprods (l : (int*int) list) : int list =

        match lwith

        | [] -> []

        | (x,y)::tl ->


    A more interesting example6

    A more interesting example

    We know it has to be an int list. So ?1 should be an int, and ?2 should be an int list.

    • Given a list of pairs of integers, produce the list of products of the pairs.

      • e.g., Given [(2,3); (4,7); (5,2)] return [6; 28; 10]

        let recprods (l : (int*int) list) : int list =

        match lwith

        | [] -> []

        | (x,y)::tl -> ?1 :: ?2


    A more interesting example7

    A more interesting example

    • Given a list of pairs of integers, produce the list of products of the pairs.

      • e.g., Given [(2,3); (4,7); (5,2)] return [6; 28; 10]

        let recprods (l : (int*int) list) : int list =

        match lwith

        | [] -> []

        | (x,y)::tl -> (x*y) :: ?2


    A more interesting example8

    A more interesting example

    • Given a list of pairs of integers, produce the list of products of the pairs.

      • e.g., Given [(2,3); (4,7); (5,2)] return [6; 28; 10]

        let recprods (l : (int*int) list) : int list =

        match lwith

        | [] -> []

        | (x,y)::tl -> (x*y) :: (prods tl)


    Moral

    Moral

    let recprods (l : (int*int) list) : int list =

    match lwith

    | [] -> []

    | (x,y)::tl -> (x*y) :: (prods tl)

    Writing down the types guides the construction of a function.

    In general, we tear apart inputs using pattern matching (or let) and then build results using the constructors.

    We’re not done: we should test it!

    what tests would you pick? why?


    Another example

    Another example

    Write a function zip which when given two lists of integers, returns an optional list of the pairs of the integers. We should return Some answer when the lists are of the same size, and None otherwise.

    Examples: zip [1;2;3] [4;5;6] = Some [(1,4);(2,5);(3,6)] zip [1;2;3] [4;5;6;7;8] = None zip [1;2;3] [4] = None


    Start by writing the signature

    Start by writing the signature

    let rec zip (x:int list) (y:int list) : ((int*int) list) option =


    Tear apart the inputs 4 cases

    Tear apart the inputs: 4 cases

    let rec zip (x:int list) (y:int list) : ((int*int) list) option =

    match (x,y) with

    | ([],[]) ->

    | ([],yhd::ytl) ->

    | (xhd::xtl,[]) ->

    | (xhd::xtl,yhd::ytl) ->

    Note: if you leave off a case, ML will warn you. Similarly, if you add a redundant case, ML will warn you. We won’t accept code with such warnings!


    Solve the easy cases

    Solve the easy cases

    let rec zip (x:int list) (y:int list) : ((int*int) list) option =

    match (x,y) with

    | ([],[]) -> Some []

    | ([],yhd::ytl) -> None

    | (xhd::xtl,[]) -> None

    | (xhd::xtl,yhd::ytl) ->


    Can we just call zip recursively

    Can we just call zip recursively?

    let rec zip (x:int list) (y:int list) : ((int*int) list) option =

    match (x,y) with

    | ([],[]) -> Some []

    | ([],yhd::ytl) -> None

    | (xhd::xtl,[]) -> None

    | (xhd::xtl,yhd::ytl) -> (xhd,yhd) :: (zip xtlytl)


    Can we just call zip recursively1

    Can we just call zip recursively?

    let rec zip (x:int list) (y:int list) : ((int*int) list) option =

    match (x,y) with

    | ([],[]) -> Some []

    | ([],yhd::ytl) -> None

    | (xhd::xtl,[]) -> None

    | (xhd::xtl,yhd::ytl) -> (xhd,yhd) :: (zip xtlytl)

    No – zip returns an optional list. We need to match it to cover the two cases where it returns None or Some.


    Solution

    Solution?

    let rec zip (x:int list) (y:int list) : ((int*int) list) option =

    match (x,y) with

    | ([],[]) -> Some []

    | ([],yhd::ytl) -> None

    | (xhd::xtl,[]) -> None

    | (xhd::xtl,yhd::ytl) ->

    (match zip xtlytlwith

    | None -> None

    | Some ztl -> (xhd,yhd)::ztl)


    Oops need a some

    Oops! Need a Some.

    let rec zip (x:int list) (y:int list) : ((int*int) list) option =

    match (x,y) with

    | ([],[]) -> Some []

    | ([],yhd::ytl) -> None

    | (xhd::xtl,[]) -> None

    | (xhd::xtl,yhd::ytl) ->

    (match zip xtlytlwith

    | None -> None

    | Some ztl -> Some((xhd,yhd)::ztl)


    And then cleanup a bit

    And then cleanup a bit

    Recall “_” matches anything. Earlier patterns match before later ones.

    let rec zip (x:int list) (y:int list) : ((int*int) list) option =

    match (x,y) with

    | ([],[]) -> Some []

    | (xhd::xtl,yhd::ytl) ->

    (match zip xtlytlwith

    | None -> None

    | Some ztl -> Some((xhd,yhd)::ztl))

    | (_,_) -> None


    Quick recap

    Quick Recap:

    basic types: int, float, char, string, …

    tuples: (3,true,”Greg”) let (a,b,c) = a_triple in …

    options: None match an_option with

    Some 3 | None -> … | Some i -> …

    lists: []match a_list with

    3::[] | [] -> … [1;2;3;4] | hd::tl -> …

    Formula for writing functions: 1. write down types of arguments & results2. using types, deconstruct arguments into cases3. solve each of the sub-cases, paying attention to types.


    Some problems to practice

    Some problems to practice:

    • Write a function to sum the elements in a list:

      • sum [1;2;3] = 6

    • Write a function to append two lists:

      • append [1;2;3] [4;5;6] = [1;2;3;4;5;6]

    • Write a function to reverse a list:

      • rev [1;2;3] = [3;2;1]

    • Write a function to split a list of pairs of integers into a pair of lists of integers:

      • split [(1,2); (3,4); (5,6)] = ([1;3;5], [2;4;6])

    • Write a function that returns all prefixes of a list:

      • prefixes [1;2;3;4] = [[];[1];[1;2];[1;2;3];[1;2;3;4]]


    Records

    Records

    • You must declare a record type before using it:

      type employee = {name:string ; age:int ; married:bool} ;;

    • Once declared, employee values look like this:

      • let g : employee = {name=“Greg”; married=true; age=112} ;;

      • let v = {married=false; name=“Victor”; age=77} ;;

      • let w = {name=“Gideon”; age=-12; married=false} ;;

      • let db : employee list = [g; v; w]

    • Note that the order of the fields doesn’t matter.

      • Ocaml knows the order from the declaration.

    • And a given label (e.g., “name”, “age”) can only belong to one record at a time.

      • Modules namespaces will help down the road.

      • Necessary for Ocaml to figure out the types.


    Field access

    Field Access

    • Similar to tuples, can use a record pattern to tear apart a record as in:

      let age_diff (e1:employee) (e2:employee) =

      let {name=n1; age=a1; married=m1} = e1 in

      let {name=n2; age=a2; married=m2} = e2 in

      a2 – a1


    Field access1

    Field Access

    • Ocaml doesn’t need the type declarations to figure out that e1 and e2 must be employees – the patterns we use are enough for type inference to figure things out.

      let age_diff e1 e2 =

      let {name=n1; age=a1; married=m1} = e1 in

      let {name=n2; age=a2; married=m2} = e2 in

      a2 – a1

    • Rule of thumb: like comments, put types in your code to make it easier for a human to understand your code.

      • For simple code fragments, it can be distracting, so leave it out.

      • For top-level code, or tricky code, it can be a great help!


    Record patterns

    Record Patterns

    • Sometimes, you don’t want all the fields.

    • You can omit those that aren’t used:

      let age_diff e1 e2 =

      let {age=a1} = e1 in

      let {age=a2} = e2 in

      a2 – a1


    Some syntactic sugar

    Some Syntactic Sugar

    • We can use the “e.id” notation to access field id – it’s just shorthand for “let {id=x}=e in x”.

      So:

      let age_diff e1 e2 = e2.age – e1.age ;;

      is equivalent to:

      let age_diff e1 e2 =

      (let {age=a2}=e2 in a2) –

      (let {age=a1}=e1 in a1) ;;


    Exercise

    Exercise

    • Write a function that sorts a list of employee records by age: sort [{name=“greg”; married=true; age=112} ;{name=“victor”; married=false; age=42} ;{name=“gideon”; married=false; age=-3} ] = [{name=“gideon”; married=false; age=-3} ; {name=“victor”; married=false; age=42} ; {name=“greg”; married=true; age=112}]

      • Note: this is not a great specification – what’s missing?


    Bad solution

    (Bad) Solution

    • Let’s do an insertion sort.

      • i.e., given a list [e1;e2;e3] insert each element of the list in the right order into a resulting list.

      • this will be bad from a complexity view

      • but easy to understand

    • What’s the first step?

      • Let’s first decompose the problem into two simpler functions:

      • insert: takes an employee and inserts it into a list at the right age position.

      • insert_list: takes a list of employees as input and inserts each one into a list to get a sorted list.

      • That is: sort l = insert_listl []


    Insert into a list

    Insert into a List

    insert: given an employee e and a list of employees db, returns a new list that includes e and all the elements of db, in sorted order.


    Insert into a list1

    Insert into a List

    insert: given an employee e and a list of employees db, returns a new list that includes e and all the elements of db, in sorted order.

    let recinsert (e:employee) (db:employee list) : employee list =


    Insert into a list2

    Insert into a List

    insert: given an employee e and a list of employees db, returns a new list that includes e and all the elements of db, in sorted order.

    let recinsert (e:employee) (db:employee list) : employee list =

    match (e, db) with

    | ({name=n;age=a;married=m}, []) ->

    | ({name=n;age=a;married=m}, dbhd :: dbtl) ->


    Insert into a list3

    Insert into a List

    insert: given an employee e and a list of employees db, returns a new list that includes e and all the elements of db, in sorted order.

    let recinsert (e:employee) (db:employee list) : employee list =

    let {name=n; age=a; married=m} = ein

    match db with

    | [] -> e::[]

    | dbhd :: dbtl ->


    Insert into a list4

    Insert into a List

    insert: given an employee e and a list of employees db, returns a new list that includes e and all the elements of db, in sorted order.

    let recinsert (e:employee) (db:employee list) : employee list =

    let {name=n; age=a; married=m} = ein

    match db with

    | [] -> e::[]

    | dbhd :: dbtl ->

    let {name=nhd; age=ahd; married=mhd} = dbhdin


    Insert into a list5

    Insert into a List

    insert: given an employee e and a list of employees db, returns a new list that includes e and all the elements of db, in sorted order.

    let recinsert (e:employee) (db:employee list) : employee list =

    let {name=n; age=a; married=m} = ein

    match db with

    | [] -> e::[]

    | dbhd :: dbtl ->

    let {name=nhd; age=ahd; married=mhd} = dbhdin

    if a <= ahdthen

    else


    Insert into a list6

    Insert into a List

    insert: given an employee e and a list of employees db, returns a new list that includes e and all the elements of db, in sorted order.

    let recinsert (e:employee) (db:employee list) : employee list =

    let {name=n; age=a; married=m} = ein

    match db with

    | [] -> e::[]

    | dbhd :: dbtl ->

    let {name=nhd; age=ahd; married=mhd} = dbhdin

    if a <= ahdthen

    e :: db

    else


    Insert into a list7

    Insert into a List

    insert: given an employee e and a list of employees db, returns a new list that includes e and all the elements of db, in sorted order.

    let recinsert (e:employee) (db:employee list) : employee list =

    let {name=n; age=a; married=m} = ein

    match db with

    | [] -> e::[]

    | dbhd :: dbtl ->

    let {name=nhd; age=ahd; married=mhd} = dbhdin

    if a <= ahdthen

    e :: db

    else

    dbhd :: (insert edbtl)


    Using the notation

    Using the “.” notation:

    insert: given an employee e and a list of employees db, returns a new list that includes e and all the elements of db, in sorted order.

    let recinsert (e:employee) (db:employee list) : employee list =

    match db with

    | [] -> e::[]

    | dbhd :: dbtl ->

    if e.age <= dbhd.agethen

    e :: db

    else

    dbhd :: (insert edbtl)


    Now insert list

    Now insert_list

    insert_list should take an input list db1 and an output list db2, and insert all of the employees from db1 into db2.


    Now insert list1

    Now insert_list

    insert_list should take an input list db1 and an output list db2, and insert all of the employees from db1 into db2.

    let recinsert_list (db1:employee list) (db2:employee list) : employee list =


    Now insert list2

    Now insert_list

    insert_list should take an input list db1 and an output list db2, and insert all of the employees from db1 into db2.

    let recinsert_list (db1:employee list) (db2:employee list) : employee list =

    match (db1, db2) with

    | ([], []) ->

    | ([], db2hd::db2tl) ->

    | (db1hd::db1tl, []) ->

    | (db1hd::db1tl, db2hd::db2tl) ->


    Now insert list3

    Now insert_list

    insert_list should take an input list db1 and an output list db2, and insert all of the employees from db1 into db2.

    let recinsert_list (db1:employee list) (db2:employee list) : employee list =

    match (db1, db2) with

    | ([], []) -> []

    | ([], db2hd::db2tl) -> db2hd::db2tl

    | (db1hd::db1tl, []) ->

    | (db1hd::db1tl, db2hd::db2tl) ->


    Now insert list4

    Now insert_list

    insert_list should take an input list db1 and an output list db2, and insert all of the employees from db1 into db2.

    let recinsert_list (db1:employee list) (db2:employee list) : employee list =

    match (db1, db2) with

    | ([], _) -> db2

    | (db1hd::db1tl, []) ->

    | (db1hd::db1tl, db2hd::db2tl) ->


    Now insert list5

    Now insert_list

    insert_list should take an input list db1 and an output list db2, and insert all of the employees from db1 into db2.

    let recinsert_list (db1:employee list) (db2:employee list) : employee list =

    match (db1, db2) with

    | ([], _) -> db2

    | (db1hd::db1tl, []) ->

    | (db1hd::db1tl, db2hd::db2tl) ->

    insert_list db1tl (insert db1hd (db2hd::db2tl))


    Now insert list6

    Now insert_list

    insert_list should take an input list db1 and an output list db2, and insert all of the employees from db1 into db2.

    let recinsert_list (db1:employee list) (db2:employee list) : employee list =

    match (db1, db2) with

    | ([], _) -> db2

    | (db1hd::db1tl, []) ->

    | (db1hd::db1tl, _) ->

    insert_list db1tl (insert db1hd db2)


    Now insert list7

    Now insert_list

    insert_list should take an input list db1 and an output list db2, and insert all of the employees from db1 into db2.

    let recinsert_list (db1:employee list) (db2:employee list) : employee list =

    match (db1, db2) with

    | ([], _) -> db2

    | (db1hd::db1tl, _) ->

    insert_list db1tl (insert db1hd db2)

    | (db1hd::db1tl, _) ->

    insert_list db1tl (insert db1hd db2)


    Now insert list8

    Now insert_list

    insert_list should take an input list db1 and an output list db2, and insert all of the employees from db1 into db2.

    let recinsert_list (db1:employee list) (db2:employee list) : employee list =

    match (db1, db2) with

    | ([], _) -> db2

    | (db1hd::db1tl, _) ->

    insert_list db1tl (insert db1hd db2)


    Now insert list9

    Now insert_list

    insert_list should take an input list db1 and an output list db2, and insert all of the employees from db1 into db2.

    let recinsert_list (db1:employee list) (db2:employee list) : employee list =

    match db1 with

    | [] -> db2

    | db1hd::db1tl ->

    insert_list db1tl (insert db1hd db2)


    Now insert list10

    Now insert_list

    insert_list should take an input list db1 and an output list db2, and insert all of the employees from db1 into db2.

    let recinsert_list (db1:employee list) (db2:employee list) : employee list =

    match db1 with

    | [] -> db2

    | db1hd::db1tl -> insert_list db1tl (insert db1hd db2) ;;

    let sort db = insert_list db [] ;;


    One more key type

    One more key type

    So far, we’ve seen:

    • primitive types: int, char, string, etc.

    • tuples and their more general cousins records.

    • options and lists

      Turns out that this last category isn’t really built into Ocaml, but can be defined.

      What do they have in common?

      • more than one way to build them (None vs Some; [] vs ::)

      • need to eliminate them using match – can’t get away with let.


    Defining algebraic datatypes in ocaml

    Defining algebraic datatypes in Ocaml

    Datatype declarations look like this:

    type int_list = Null | Cons of int*(int_list) ;;type boolean = True | False ;;

    type color = Red | Green | Blue ;;

    type int_maybe = Nothing | Just of int ;;

    type value =

    Intof int | Strof string | Boolof bool

    | Pair of value*value ;;


    Defining algebraic datatypes in ocaml1

    We call Null, Cons, True, False, Red, Green, etc. datatype constructors as they are used to build values of the corresponding type.

    Defining algebraic datatypes in Ocaml

    Datatype declarations look like this:

    type int_list = Null | Cons of int*(int list) ;;type boolean = True | False ;;

    type color = Red | Green | Blue ;;

    type int_maybe = Nothing | Just of int ;;

    type value =

    Intof int | Strof string | Boolof bool

    | Pair of value*value ;;


    Defining algebraic datatypes in ocaml2

    Defining algebraic datatypes in Ocaml

    You can read this definition as saying that a value is either an int, string, bool, or pair of values.

    (notice the definition is recursive!)

    Datatype declarations look like this:

    type int_list = Null | Cons of int*(int list) ;;type boolean = True | False ;;

    type color = Red | Green | Blue ;;

    type int_maybe = Nothing | Just of int ;;

    type value =

    Intof int | Strof string | Boolof bool

    | Pair of value*value ;;


    Defining algebraic datatypes in ocaml3

    Defining algebraic datatypes in Ocaml

    So an int_list is either Null or a Cons of a pair of an int and int_list. It’s also recursive!

    Datatype declarations look like this:

    type int_list = Null | Cons of int*(int_list) ;;type boolean = True | False ;;

    type color = Red | Green | Blue ;;

    type int_maybe = Nothing | Just of int ;;

    type value =

    Intof int | Strof string | Boolof bool

    | Pair of value*value ;;


    Defining algebraic datatypes in ocaml4

    Defining algebraic datatypes in Ocaml

    When data constructors don’t carry information with them, we omit the “of type”

    Datatype declarations look like this:

    type int_list = Null | Cons of int*(int_list) ;;type boolean = True | False ;;

    type color = Red | Green | Blue ;;

    type int_maybe = Nothing | Just of int ;;

    type value =

    Intof int | Strof string | Boolof bool

    | Pair of value*value ;;


    Building datatype values

    Building Datatype Values

    We can build datatypes using the data constructors – simply apply them like functions to arguments of the right type.

    type value =

    Intof int | Strof string | Boolof bool

    | Pair of value*value ;;

    let v1 : value = Int3 ;;

    let v2 : value = Booltrue ;;

    let v3 : value = Pair (v1,v2) ;;

    let v4 : value = Pair (Pair (Int12, Boolfalse),

    Pair (v3, Str“moo”)) ;;

    let vs : value list = [v1;v2;v3] ;;


    Tearing apart datatype values

    Tearing Apart Datatype Values

    To tear them apart, we must use match with a case for each data constructor:

    type value =

    Intof int | Strof string | Boolof bool

    | Pair of value*value ;;

    let recall_strings (v:value) : string list =

    matchvwith

    | Inti ->

    | Strs ->

    | Boolb ->

    | Pair (v1,v2) ->


    Tearing apart datatype values1

    Tearing Apart Datatype Values

    [email protected] is the list append operator.

    To tear them apart, we must use match with a case for each data constructor:

    type value =

    Intof int | Strof string | Boolof bool

    | Pair of value*value ;;

    let recall_strings (v:value) : string list =

    matchvwith

    | Inti -> []

    | Strs -> s::[]

    | Boolb -> []

    | Pair (v1,v2) ->

    (all_strings v1) @ (all_strings v2);;


    Example type design

    Example Type Design:

    • A GML document consists of:

      • a list of elements

    • An element is either:

      • a word, markup applied to a sub-document,

    • Markup is either:

      • italicize, bold, or a font name


    Example

    Example:

    doc doesn’t have any data constructors so it’s simply a type abbreviation.

    • A GML document consists of a list of elements, where an element is either:

      • a list of words, or markup applied to an element,

    • Markup is either:

      • italicize, bold, or a font name

        type markup = Ital | Bold | Font of string ;;

        type elt = Words of string list | Markup of markup * elt ;;

        type doc = elt list ;;


    Example1

    Example:

    type markup = Ital | Bold | Font of string ;;

    type elt = Words of string list | Markup of markup * elt ;;

    type doc = elt list ;;

    let d = [ Markup (Bold,

    Markup (Font “Arial”, Words [“Chapter”;“One”])) ;

    Words [“It”;”was”;”a”;”dark”;”&”; ”stormy;”night.”;”A”] ;

    Markup (Ital, Words[“shot”]) ;

    Words [“rang”;”out.”] ] ;;


    Challenge

    Challenge:

    • Change all of the “Arial” fonts in a document to “Courier”.

      let recchfonts (elts:doc) : doc =

      match eltswith

      | [] -> []

      | ehd::etl ->

      (chfontehd)::(chfontsetl)


    Changing fonts in an element

    Changing fonts in an element

    type markup = Ital | Bold | Font of string ;;

    type elt = Words of string list | Markup of markup * elt

    let recchfont (e:elt) : elt =


    Changing fonts in an element1

    Changing fonts in an element

    type markup = Ital | Bold | Font of string ;;

    type elt = Words of string list | Markup of markup * elt

    let recchfont (e:elt) : elt =

    match ewith

    | Words ws ->

    | Markup(m,e) ->


    Changing fonts in an element2

    Changing fonts in an element

    type markup = Ital | Bold | Font of string ;;

    type elt = Words of string list | Markup of markup * elt

    let recchfont (e:elt) : elt =

    match ewith

    | Words ws -> Words ws

    | Markup(m,e) ->


    Changing fonts in an element3

    Changing fonts in an element

    type markup = Ital | Bold | Font of string ;;

    type elt = Words of string list | Markup of markup * elt

    let recchfont (e:elt) : elt =

    match ewith

    | Words ws -> Words ws

    | Markup(m,e) -> Markup(chmarkupm, chfonte)


    Changing fonts in an element4

    Changing fonts in an element

    type markup = Ital | Bold | Font of string ;;

    type elt = Words of string list | Markup of markup * elt

    let chmarkup (m:markup) : markup =

    match mwith

    | Font “Arial” -> Font “Courier”

    | _ -> m

    let recchfont (e:elt) : elt =

    match ewith

    | Words ws -> Words ws

    | Markup(m,e) -> Markup(chmarkupm, chfonte)


    Not great style

    Not great style:

    type markup = Ital | Bold | Font of string ;;

    let chmarkup (m:markup) : markup =

    match mwith

    | Font “Arial” -> Font “Courier”

    | _ -> m


    Not great style1

    Not great style:

    type markup = Ital | Bold | Font of string | TTFontof string ;;

    let chmarkup (m:markup) : markup =

    match mwith

    | Font “Arial” -> Font “Courier”

    | _ -> m


    Not great style2

    Not great style:

    type markup = Ital | Bold | Font of string ;;

    let chmarkup (m:markup) : markup =

    match mwith

    | Font “Arial” -> Font “Courier”

    | (Ital | Bold | Font _) -> m


    Not great style3

    Not great style:

    Warning: missing case for (TTfont _)

    type markup = Ital | Bold | Font of string

    | TTFontof string;;

    let chmarkup (m:markup) : markup =

    match mwith

    | Font “Arial” -> Font “Courier”

    | TTFont “Arial” -> TTFont“Courier”

    | (Ital | Bold | Font _) -> m


    To summarize

    To Summarize:

    • Design recipe for writing Ocaml code:

      • write down English specifications

        • try to break problem into obvious sub-problems

      • write down some sample test cases

      • write down the signature (types) for the code

      • use the signature to guide construction of the code:

        • tear apart inputs using pattern matching

          • make sure to cover all of the cases! (Ocaml will tell you)

        • handle each case, building results using data constructor

          • this is where human intelligence comes into play

          • the “skeleton” given by types can almost be done automatically!

        • clean up your code

      • use your sample tests (and ideally others) to ensure correctness


    More practice problems

    More practice problems:

    Write a function that merges two age-sorted lists of employees into a single age-sorted list.

    Write a function that, when given a name N and list of employees db, looks up N’s record in db and returns N’s age. (Hint: what if N isn’t in db?)

    Write a function that gets rid of immediately redundant markup in a document. That is, Markup(Ital, Markup(Ital,e)) can be simplified to Markup(Ital,e).

    Design a datatype to describe bibliography entries for publications. Some publications are journal articles, others are books, and others are conference papers. Journals have a name, number and issue; books have an ISBN number; All of these entries should have a title and author.


  • Login