Record Lectures 3 1 3 Sezon

broken image


  1. Record Lectures 3 1 3 Sezona
  2. Record Lectures 3 1 3 Sezona 2
  • SML Lists
  • Recursive datatypes
  • Implementing lists with recursive datatypes
  • Other uses of recursive datatypes: trees, etc.

Missing the Lecture Format in an Online Class Online learning changes the way you interact with your professor. By Devon Haynie, Assistant Managing Editor, Cities Nov. II.Record Lectures on Windows and Mac with ApowerREC Documenting lectures come handy especially when teaching multiple classes. Aside from making sure that the recorded class lectures is vivid, you can also save a lot of time in preparing for each class when you already have a recorded video of the lecture at hand.

Lists

So far the only real data structures we can build are made of tuples. But tuples don't let us make data structures whose size is not known at compile time. For that we need a new language feature. One simple data structure that we're used to is singly linked lists. It turns out that SML has lists built in. For example, in SML the expression [] is an empty list. The expression [1,2,3] is a list containing three integers. Another name for the empty list is nil. This is just the same thing as [].

Impact Stratigraphy. The Italian Record. Lecture Notes in Earth Sciences Series no. Berlin, Heidelberg, New York. The recording also stops when everyone leaves the meeting. Click Stop recording again to verify. Wait for the recording file to be generated and saved to the meeting organizer's My Drive Meet Recordings folder. An email with the recording link is sent to the meeting organizer, and the person who started the recording. Compatibility: OS X 10.8 or later 64 bit. Record Lectures was Featured by Apple in 'Best New Apps' and as one of the best apps for ' Back to school' Record Lectures was built with the purpose of allowing anyone who records lectures, meetings and conversations to be able to store them in a calendar and be able to share these recordings easily.

In SML, all the elements of a list have to have the same type. For example, a list of integers has the type int list. Similarly, the list ['hi', 'there', '312'] would have the type string list. But [1, 'hi'] is not a legal list. Lists in SML are therefore homogeneous lists, as opposed to heterogeneous lists in which each element can have a different type.

Lists are immutable: you cannot change the elements of a list, unlike an array in Java.

Constructing lists

Often we want to make a list out of smaller lists. We can concatenate two lists with the @ operator. For example, [1,2,3] @ [4,5,6] = [1,2,3,4,5,6]. However, this operator isn't very fast because it needs to build a copy of the entire first list. More often we use the :: operator, which prepends an element to the front of an existing list ('prepend' means 'append onto the front'). The expression 1::[2,3] is 1 prepended onto the list [2,3]. This is just the list [1,2,3]. For historical reasons going back to the language Lisp, we usually call the :: operator 'cons'.

The fact that lists are immutable is in keeping with SML being a functional language. It is also actually useful for making SML more efficient, because it means that different list data structures can share parts of their representation in the computer's memory. For example, evaluating h::t only requires allocating space for a single extra list node in the computer's memory. It shares the rest of the list with the existing list t.

Pattern matching on lists

Sezona

The best way to extract elements from a list is to use pattern matching. Cisdem pdf converter 7 1 0 64. The operator :: and the bracket constructor can be used as patterns in a case expression. For example, if we wanted to get the value 1 if we had a list of one element, and zero if we had an empty list, we could write:

Here, x would be bound to the single element of the list if the second case arm were evaluated.

Often, functions that manipulate lists are recursive, because they need to do something to every element. For example, suppose that we wanted to compute the length of a list of strings. We could write a recursive function that accomplishes this (in fact, the library function List.length does just this):

The logic here is that if a list is empty ([]), its length is clearly zero. Otherwise, if it is the appending of an element h onto another list t, its length must be one greater than the length of t.

It's possible to write patterns using the bracket syntax. This is exactly the same as writing a similar pattern using the :: operator. For example, the following patterns are all equivalent: [x,2], x::2::nil, x::2::[], x::[2]. These expressions are also all equivalent when used as terms.

Library functions

The SML structure List contains many useful functions for manipulating lists. Before using lists, it's worth taking a look. Some of them we'll talk about later in more detail. Two functions that should be used with caution are hd and tl. These functions get the head and tail of a list, respectively. However, they raise an exception if applied to an empty list. They make it easy to forget about the possibility that the list might be empty, creating expected exceptions that crash your program. So it's usually best to avoid them.

List examples

We can use pattern matching to implement other useful functions on lists. Suppose we wanted a function that would extract a list element by its index within the list, with the first element at index zero. We can implement this neatly by doing a pattern match on the list and the integer n at the same time:

Recursive datatypes

Lists are very useful, but it turns out they are not really as special as they look. We can implement our own lists, and other more interesting data structures, such as binary trees.

In recitation you saw some simple examples of datatypes, which are SML types that can have more than one kind of value. This involved a new kind of declaration, a datatype declaration (where we've written brackets like this to indicate optional syntax: [ ])

datatypeY= X1[of t1] | .. |Xn [of tn]

and new term and pattern forms:

e ::= .. |X(e)|case e of p1=>e1| .. | pn=>en
p ::= X |X(x1:t1..,xn:tn)

Note that the vertical bars in the expression 'case e of p1=>e1 | .. | pn=>en' are part the syntax of this construct; the other vertical bars (|) are part of the BNF notation. We can use datatypes to define many useful data structures. We saw in recitation that the bool type is really just a datatype, so we don't need to have booleans built into SML the way we do in Java.

Implementing integer lists

We can write our own version of lists using datatypes. Suppose we want to define values that act like linked lists of integers. A linked list is either empty, or it has an integer followed by another list containing the rest of the list elements. This leads to a very natural datatype declaration:

This datatype has two constructors, Nil and Cons. It is a recursive datatype because it mentions itself in its own definition (in the Cons constructor), just like a recursive function is one that mentions itself in its own definition.

Any list of integers can be represented by using this datatype. For example, the empty list is just the constructor Nil, and Cons corresponds to the operator ::. Here are some examples of lists:

So we can construct any lists we want. We can also take them apart using pattern matching. For example, our length function above can be written for our lists by just translating the list patterns into the corresponding patterns using datatype constructors:

Similarly, we can implement many other functions over lists, as shown in the following examples.

In fact, if we want our lists to look even more like SML lists, we could even overload the :: operator.

Operator overloading can be convenient, but it is also dangerous because it can confuse someone trying to read the code. This particular use is probably not worth it.

Representing trees with a recursive datatype

Trees are another very useful data structure, and unlike lists, they are not built into SML. A binary tree is a node containing a value and two children that are trees. A binary tree can also be an empty tree, which we also use to represent the absence of a child node. We can write this down directly as a datatype:

We can use this datatype to represent any binary tree:

Because there are several things stored in a tree node, it's helpful to use a record rather than a tuple to keep them all straight. But a tuple would also have worked.

We can use pattern matching to write the usual algorithms for recursively traversing trees. For example, here is a recursive search over the tree:

Of course, if we knew the tree obeyed the binary search tree invariant, we could have written a more efficient algorithm.

Representing natural numbers with a recursive datatype

We can even define data structures that act like numbers, demonstrating that we don't really have to have numbers built into SML either! A natural number is either the value zero or the successor of some other natural number. This definition leads naturally to the following definition for values that act like natural numbers nat:

This is how you might define the natural numbers in a mathematical logic course. We have defined a new type nat, and Zero and Succ are constructors for values of this type. This datatype is different than the ones we saw in recitation: the definition of nat refers to nat itself. In other words, this is a recursive datatype. This allows us to build expressions that have an arbitrary number of nested Succ constructors. Such values act like natural numbers:

When we ask the compiler what four represents, we get

Thus four is a nested data structure. The equivalent Java definitions would be

And in fact the Java objects representing the various numbers are actually implemented similarly to the SML values representing the corresponding numbers.

Record Lectures 3 1 3 Sezona

Now we can write functions to manipulate values of this type.

The case expression allows us to do pattern matching on expressions. Here we're pattern-matching a value of type nat. If the value is Zero we evaluate to true; otherwise we evaluate to false.

Record Lectures 3 1 3 Sezona 2

Here we determine the predecessor of a number. If the value of n matches Zero then we raise an exception, since zero has no predecessor in the natural numbers. If the value matches Succ(m) for some value m (which of course also must be of type nat), then we return m.

Similarly we can define a function to add two numbers: (See if the students can come up with this with some coaching.)

If you were to try evaluating add(four,four), the compiler would respond with:

The compiler correctly performed the addition, but it has abbreviated the output because the data structure is nested so deeply. To easily understand the results of our computation, we would like to convert such values to type int:

That was pretty easy. Now we can write toInt(add(four,four)) and get 8. How about the inverse operation?

To determine whether a natural number is even or odd, we can write a pair of mutually recursive functions:

You have to use the keyword and to combine mutually recursive functions like this. Otherwise the compiler would flag an error when you refer to odd before it has been defined.

Finally we can define multiplication in terms of addition. (See if the students can figure this out.)

Deep pattern matching

It turns out that the syntax of ML patterns is richer than what we saw in the last lecture. In addition to new kinds of terms for creating and projecting tuple and record values, and creating and examining datatype values, we also have the ability to match patterns against values to pull them apart into their parts.

When used properly, ML pattern matching leads to concise, clear code. This is because ML supports deep pattern matching in which one pattern can appear as a subexpression of another pattern. For example, we see above that Succ(n) is a pattern, but so is Succ(Succ(n)). This second pattern matches only on a value that has the form Succ(Succ(v)) for some value v (that is, the successor of the successor of something), and binds the variable n to that something, v.

Similarly, in our implementation of the nth function, earlier, a neat trick is to use deep pattern matching to do the if n=0 and the case at the same time. We pattern-match on the tuple (lst, n):

Here, we've also added a clause to catch the empty list and raise an exception. We're also using the wildcard pattern _ to match on the n component of the tuple, because we don't need to bind the value of n to another variable—we already have n. We can make this code even shorter; can you see how?

Example: pattern matching on records

Natural numbers aren't quite as good as integers, but we can simulate integers in terms of the naturals by using a representation consisting of a sign and magnitude:

The type keyword simply defines a name for a type. Here we've defined integer to refer to a record type with two fields: sign and mag. Remember that records are unordered, so there is no concept of a 'first' field.

Note that a type declaration is different from a datatype declaration; it creates a new way to name an existing type, whereas a datatype declaration creates a new type and also happens to give it a name, which is needed to support recursion. For example, we could write a declarationtype natural = nat. The type natural and nat would then be exactly the same type and usable interchangeably.

We can use the definition of integer to write some integers:

Now we can write a function to determine the successor of any integer:

Here we're pattern-matching on a record type. Notice that in the third pattern we are doing deep pattern matching because the mag field is matched against a pattern itself, Succ(n). Remember that the patterns are tested in order. How does the meaning of this function change if the first two patterns are swapped?

The predecessor function is very similar, and it should be obvious that we could write functions to add, subtract, and multiply integers in this representation.

SML syntax

Taking into account the ability to write complex patterns, we can now write down a more complete syntax for SML.

syntactic classsyntactic variables and grammar rule(s) examples
identifiersx, ya, x, y, x_y, foo1000, ..
datatypes, datatype constructorsX, YNil, Cons, list
constantsc..~2, ~1, 0, 1, 2 (integers)
1.0, ~0.001, 3.141 (reals)
true, false (booleans)
'hello', ', '!' (strings)
#'A', #' ' (characters)
unary operatoru~, not, size, ..
binary operatorsb+, *, -, >, <, >=, <=, ^, ..
expressions (terms)e ::= c | x | u e | e1 b e2 | if e1then e2else e3 | letd1..dnineend | e(e1, ..,en)| (e1,..,en)| #n e | {x1=e1, ..,xn=en}| #x e | X(e) | case e of p1=>e1| .. | pn=>en~0.001, foo, notb, 2 + 2, Cons(2, Nil)
patterns

p ::= c | x | (p1,..,pn) | {x1=p1,..,xn=pn} | X | X (p)

a:int, (x:int,y:int), I(x:int)
declarationsd ::= valp = e |fun y p :t = e | datatypeY= X1[of t1]| .. |Xn [of tn]val one = 1
fun square(x: int): int
datatype d = N | I of int
typest ::= int | real | bool | string | char | t1->t2 | t1*..*tn | {x1:t1, x2:t2,.., xn:tn} | Yint, string, int->int, bool*int->bool
valuesv ::= c | (v1,..,vn) | {x1=v1, ..,xn=vn} | X(v)2, (2,'hello'), Cons(2,Nil)

Note: pattern-matching real constants is not possible. So in the production 'p ::= c | .' above, c is an integer, boolean, string, or character constant, but not real.





broken image