Lecture 16: Recursive Data Types

来源:互联网 发布:淘宝老猪数码坑人 编辑:程序博客网 时间:2024/04/29 12:28

1 Recursive Data Types Definition

A datatype definition:

  • an abstract datatype on the left, defined by its representation (or concrete datatype ) on the right.
  • the representation consists of variants of the datatype separated by +.
  • each variant is a constructor with zero or more named (and typed) arguments.
  • 如果等号右侧包含等号左侧的抽象数据类型,那么此定义为递归的数据类型定义。

An example is a binary tree:

Tree<E> = Empty + Node(e:E, left:Tree<E>, right:Tree<E>)

2 Functions over recursive datatypes

The pattern of implementing an operation over a recursive datatype is:

  • Declaring the operation in the abstract datatype interface
  • Implementing the operation (recursively) in each concrete variant

It sometimes goes by the unhelpful name interpreter pattern.
For example: 定义isEmpty()函数 isEmpty : ImList → boolean

isEmpty(Empty) = true
isEmpty(Cons(first: E, rest: ImList)) = false

2.1 Null vs. empty

  • Using an object, rather than a null reference, to signal the base case or endpoint of a data structure is an example of a design pattern called sentinel objects.
  • Keep null s out of your data structures, and your life will be happier.

3 Backtracking search with immutability

  • Mutable data structures are typically not a good approach for backtracking.
  • Immutable data structures with no sharing aren’t a great idea either. because the space you need to keep track of where you are (in the case of the satisfiability problem, the environment) will grow quadratically if you have to make a complete copy every time you take a new step.
  • Immutable lists have the nice property that each step taken on the path can share all the information from the previous steps.
  • A search that uses immutable data structures is immediately ready to be parallelized.

4 Writing a Program with Abstract Data Types

4.1 Writing a procedure (a static method):

  1. Spec. Write the spec, including the method signature (name, argument types, return types, exceptions), and the precondition and the postcondition as a Javadoc comment.
  2. Test. Create systematic test cases and put them in a JUnit class so you can run them automatically.
    • You may have to go back and change your spec when you start to write test cases. Just the process of writing test cases puts pressure on your spec, because you’re thinking about how a client would call the method. So steps 1 and 2 iterate until you’ve got a better spec and some good test cases.
    • Make sure at least some of your tests are failing at first. A test suite that passes all tests even when you haven’t implemented the method is not a good test suite for finding bugs.
  3. Implement. Write the body of the method. You’re done when the tests are all green in JUnit.
    • Implementing the method puts pressure on both the tests and the specs, and you may find bugs in them that you have to go back and fix. So finishing the job may require changing the implementation, the tests, and the specs, and bouncing back and forth among them.

4.2 Writing an abstract data type

  1. Spec. Write specs for the operations of the datatype, including method signatures, preconditions, and postconditions.
  2. Test. Write test cases for the ADT’s operations.
    • Again, this puts pressure on the spec. You may discover that you need operations you hadn’t anticipated, so you’ll have to add them to the spec.
  3. Implement. For an ADT, this part expands to:
    • Choose rep. Write down the private fields of a class, or the variants of a recursive datatype. Write down the rep invariant as a comment.
    • Assert rep invariant. Implement a checkRep() method that enforces the rep invariant. This is critically important if the rep invariant is nontrivial, because it will catch bugs much earlier.
    • Implement operations. Write the method bodies of the operations, making sure to call checkRep() in them. You’re done when the tests are all green in JUnit.

4.3 Writing a program (consisting of ADTs and procedures):

  1. Choose datatypes. Decide which ones will be mutable and which immutable.
  2. Choose procedures. Write your top-level procedure and break it down into smaller steps.
  3. Spec. Spec out the ADTs and procedures. Keep the ADT operations simple and few at first. Only add complex operations as you need them.
  4. Test. Write test cases for each unit (ADT or procedure).
  5. Implement simply first. Choose simple, brute-force representations. The point here is to put pressure on the specs and the tests, and try to pull your whole program together as soon as possible. Make the whole program work correctly first. Skip the advanced features for now. Skip performance optimization. Skip corner cases. Keep a to-do list of what you have to revisit.
  6. Reimplement and iterate and optimize. Now that it’s all working, make it work better

Reference

[1] 6.005 — Software Construction on MIT OpenCourseWare | OCW 6.005 Homepage at https://ocw.mit.edu/ans7870/6/6.005/s16/

0 0