Skip to content
This repository has been archived by the owner on Aug 11, 2020. It is now read-only.

SP002 Assignment Blueprints

Joe Politz edited this page Jun 11, 2013 · 4 revisions

We should write assignments in a structured scribble format. We should support all of the @functions below in captain teach, and let this be our interface for writing assignments and courses.

#lang scribble/ct

@assignment-name{Sortacle}

@summary{When you are testing complex functions, or perhaps even relations (as
in the case of this assignment), you will need to put time and effort into
building testing oracles. In this assignment, you will develop oracles to test
purported sorting algorithms. Countries by medal count, companies by income,
students by GPA, the list goes on and on.}

@instructions{

  @comments{Using the following struct where name is a string and age is a
  number, you will build an oracle for a function sorting lists of people by
  non-decreasing age.}

  @pyret/silent{
    data Person: person(name :: String, age :: Number) end
  }

  @comments{Use the "sort" function in the language to make a correct version
  called age-sort that takes in a list of people and returns the sorted list.
  Our sorting functions will use the same contract.}

  @pyret/comment{
    age-sort :: (List<Person> -> List<Person>)
  }

}

@helper-function["Generate Input"]{

  @instructions{Write a function named generate-input that (surprise,
  surprise!) generates input. This function should take an integer length, and
  return a list of randomly aged people of that length. (You may assume an
  upper age limit of 150).  }

  @header/given{

    @description{Here is the function you'll be implementing.}
    
    @pyret-header[generate_input][n :: Number][List<Person>]

  }

  @check{

    @description{Write some test cases for generate_input.  The output is
    random, so it might be hard to test with equals.  What other properties of
    the output can you test?}  

    @solution{
      doc "Returns a list of n randomly aged people"
      NAMES = ['bob', 'janet']
      MAX_AGE = 100
      fun uniform(start, stop):
        Math.sample(Math.uniform_dist(start, stop)).floor()
      end
      [a list of random folks]
    }

  }

  @definition{

    @description{Now implement the body of generate_input.}

    @check{
      @pyret{check-equals(generate_input(4).length(), 4)
      @hint{Remember that you're supposed to generate n outputs.}
    }
    @check{
      @pyret{
        check(generate_input(100),
          fun(l): for list.some(p from l): l.first.age <> p.age end)
      }
      @hint{Remember, the ages need to be *randomly* generated.}
    }
    
  }

}

@helper-function["Check if a Sort is Valid"]{

  @header/given{

    @pyret-header[is-valid][l1 :: List<Person>][l2 :: List<Person>][Boolean]

  }

  @check{

    @description{Write some test cases for is-valid.  Think about what it means
    to be sorted in general, and what it means to be a sorted *version* of
    something else.}

    @solution{
      "Return true if l2 is a valid sorting of l1, false otherwise"

      same-length = (l1.length() == l2.length())
      same-length.
        and(... more checks ...)
    }

    @hints{
      @predicate-hint{
        @pyret{
          for list.fold(acc from true, elt from l1): 
            acc.and(l1.filter(fun(e_chk): e_chk.age == elt.age end).length() > 1)
          end
        }
        @response{
          How should a sorting function handle two people with the same age?
        }
      }
    }
  }

  @definition{
    
    @check{
      @pyret{check-true(is-valid([], []))}
      @hint{What is the sorted version of an empty list?}

    }

    @check{
      @pyret{check-true(is-valid([person('bob',5), person('alice', 5)],
                                 [person('alice',5), person('bob', 5)]))}
      @hint{What should sort do with people of the same age?}
    }
  }

}


@design-recipe{
  ...
}

Here's a more incremental-lesson approach:

@assignment{

  @description{ You are planning a trip to the grocery store, and want to make
a list and determine the total cost of the trip at the same time. Three grocery
items are defined below as an example. Their values are the cost of the item.}

  

  @incremental-excercise{

    @initial-code{
      (define NUTELLA 6.99)
      (define EGGS 2.50)
      (define BANANAS 2.50)
    }


    @description{Define two more items for your grocery list}

    @(define (mk-example-checker name)
      @code-increment[name]{
      @check/syntax{
        (match ast
         [(s-def id (? number?)) #t]
         [(s-def id e) "Remember, you should be storing a *number* as the price"]
         [(id (name n (? number?)))
          "Remember, you need to use the word 'define' when writing a definition (try (define #{id} #{n}))"]
         [(? symbol?) ...]
         [(? number?) ...]
         [id "Remember, we're trying to define a grocery item, which looks like (define NAME NUMBER)"])
      }
    })

    @mk-example-checker["example1"]
    @mk-example-checker["example2"]

    @description{Write an expression (using your newly-defined items) which
will give you the total cost of the items on your grocery list.}

    @code-increment["total"]{
      @check/result/equals{
        (+ 6.99 2.50 2.50 (get-number example1) (get-number example2))
      }
      @check/syntax{
        (define all-names (list (get-id example1) (get-id example2) 'NUTELLA 'BANANAS 'EGGS))
        (cond
          [(is-permutation? (get-all-names ast) )
           #t]
          [(< (length (get-all-names ast)) (length all-names))
           "You seem to have forgotten #{(get-missing-names)} in your list"])
      }
    }

  }

  

}
Clone this wiki locally