Tag: CS61a

  • Guide to the Scheme Interpreter Project (BYU CS 111) Part 3

    Guide to the Scheme Interpreter Project (BYU CS 111) Part 3

    Part 1 Part 2

    We are finally to the last part of building our interpreter for Scheme in Python. In Part 3 we will implement our special form functions so that we can interpret And/Or, Conditionals, and the Let form in Scheme.

    Problem 12: And & Or

    Problem 12 deals with the And and Or form. We will be implementing two functions in scheme_forms.py: do_and_form() and do_or_form(). The purpose of these functions are to determine if an expression should evaluate to true or false based off of and/or logic or if it should return a value.

    Both functions will intake expressions, which will be a scheme list of individual expressions all represented in Pair objects. Understand that each sub-part of expressions is an expression in scheme being compared to the others by either ‘and‘ or ‘or‘.

    Scheme

    (and 1 5)

    Python

    expressions = Pair(1, Pair(5, nil))

    In scheme, ‘and‘ by itself evaluates to true, so it is truthy. In contrast, ‘or‘ by itself evaluates to false. We want to make the two functions recursive so that they go through each sub-part of expressions and either returns the value (or true/false if appropriate) or goes on to the next value in the expression.

    Check a value’s truthiness using the provided functions: is_scheme_true() and is_scheme_false().

    For do_and_form(), if you evaluate any of the expressions and it is a false value, return that value. Else, put the next expression into do_and_form() and recurse on until you get to the end or you get to a false value.

    For do_or_form(), if you evaluate any of the expressions and they are truthy, return that value. Else, put the next expression into do_or_form() and recurse on until you get to the end or you get to a true value.

    If you go through the entire scheme list of expressions and your function gets to the last element in the scheme list, return the value of that last part.

    Hint: What ‘and’ & ‘or’ look like alone

    Hint: Evaluating Expressions to Get the Value

    For more help

    Problem 13: Cond Form

    First of all, lets review the structure of the cond form.

    (cond (<predicate 1> <consequent expression>)
          (<predicate 2> <consequent expression>)
          (<else> <consequent expression>))

    Each conditional expression will have 1 or more predicates and in many cases will end with an else statement, but not always. We need to implement do_cond_form() such that it takes in expressions and goes through each predicate, testing if it is true. If it is true, it will return the value of the consequent expression. If not it will check the next predicate and do the same thing until we run out of predicates or hit an else statement. If there are no more predicates and no else statement, the function will simply not return anything (None).

    The function’s argument expressions will look something like so:

    Scheme

    (cond ((> 2 3) 5)

    (else 8))

    Python

    expressions = Pair(Pair(Pair(‘>’, Pair(2, Pair(3, nil))), Pair(5, nil)), Pair(Pair(‘else’, Pair(8, nil)), nil))

    We are provided some starter code for do_cond_form():

    Starter Code w/ Added Comments

    The while loop will run until we hit the end of the list of clauses, meaning expressions.rest will eventually be nil. In the indicated spot, we need to check if the clause has a consequent expression. If it does, we need to evaluate that expression and return it.

    Else, check if test is something other than true. If it is something else, that means test (which is an evaluation of the predicate) is just a value like (12), not an expression like (< 1 5) or (else). In that case we simply need to return the evaluation of the predicate.

    Lastly, if we get through all of these tests without returning anything, then it could mean two things. Either there was no predicates that were true and there was an else statement, but it did not have a consequent expression. Or it could mean that there was a true predicate without a consequent expression. In these case simply return true.

    For more help

    Problem 14: Let Form

    The let form allows us to bind variables locally. This means that when we use let, it creates a new frame in which these variables are defined. Consider the following example from the CS 111 instructions and the added environment diagram.

    scm> (define x 5)
    x
    scm> (define y 'bye)
    y
    scm> (let ((x 42)
    ...>     (y (* x 10)))
    ...> (list x y))
    (42 50)
    scm> (list x y))
    (5 bye)
    Global Frame 
    x      5
    y      bye
    Frame f1
    y      50
    x      42

    When the let expression is finished, the frame (f1) is closed and the variables defined by let disappear from the scope of the program. For more information on how let works, consult this page from the University of Texas on the let form. The last three paragraphs are particularly useful. Also refer to the CS 111 specs for let.

    To get the let form working in our scheme interpreter, we need to implement the make_let_form() in scheme_forms.py. make_let_frame() will return the new environment with the local variables defined. It takes in two arguments: bindings and env. bindings is a scheme list of variable names and values.

    Scheme

    scm> (let ((x 5))

    …> (+ x 3))

    Python

    bindings = Pair(Pair(‘x’, Pair(5, nil)), nil)

    You are going to need to figure out how you want to iterate through bindings to get each variable name and each value to which it is bound. Hint, you can use a while loop similar to the one in Problem 13.

    Check each part of bindings using validate_form() to insure they are the correct format. If the current part of bindings you are checking is the correct format, take the variable name from it and add it to the pre-defined variable names which is a scheme list.

    Then use validate_formals() on names to make sure it is still a legitimate format for a scheme list of names.

    Next, to get the value, use scheme_eval() to evaluate the part of bindings that is the value. Add that value to the pre-defined variable values which is also a scheme list.

    At the end of it all, your function should return a new environment with the new variables and values.

    Hint: validate_form()

    Hint: Adding to a Scheme List

    Hint: Where is the variable name in bindings?

    For more help

    </end>

    Hopefully this was a sufficient help. Please submit any corrections or questions in the comments below. I will try my best to answer and update the article accordingly.

  • Guide to the Scheme Interpreter Project (BYU CS 111) Part 2

    Guide to the Scheme Interpreter Project (BYU CS 111) Part 2

    Part 1 if you missed it

    In part 2 we will be implementing the code for procedures. By the end of this, you will be able to interpret user-defined functions, lambda functions, and something special called “Mu-procedures” that you will soon learn to dislike. This part will be handling problems 6-11. Good luck.

    In Scheme, we have the ‘begin’ special form. This lets us evaluate through a series of expressions in scheme and return only the value of the last expression.

    Excerpt from Berkeley CS61A Documentation

    It is useful because it allows for side effects from the first expressions to take place while returning only the evaluated value of the last expression in the series. For examples, see the examples in the CS 111 instructions and for even more understanding, read the short documentation for MIT/GNU’s implementation of scheme. Note from the documentation, that writing begin is often pointless because many special forms already apply it implicitly.

    What you need to do, is implement the eval_all() function in scheme_eval_apply.py. Eval_all() takes in two arguments: expressions and env.

    Scheme

    (1 2 3)

    Python

    expressions = Pair(1, Pair(2, Pair(3, nil)))

    You should go through expressions recursively, evaluating each one using scheme_eval(). When you get to the last expression, evaluate it like the others, but also return its value.

    For more help

    Problem 7: Lambda Procedures

    We need to implement the do_lambda_form() function in scheme_forms.py. This function takes in two arguments: expressions and env.

    expressions is going to be objects of class Pair representing our formals (just operators for the lambda function) and the second part is the operations.

    The variable formals is given to us: formals = expressions.first

    Scheme ———————

    (lambda (x y) (+x y))

    Python ———————–

    expressions = Pair(Pair(‘x’, Pair(‘y’, nil)), Pair(Pair(‘+’, Pair(‘x’, Pair(‘y’, nil))), nil))

    formals = Pair(‘x’, Pair(‘y’, nil)) #These are the arguments/operators to the lambda func

    expressions.rest = Pair(Pair(‘+’, Pair(‘x’, Pair(‘y’, nil))), nil) # the operations

    What we need to do is take the given information and return a LambdaProcedure object. The LambdaProcedure class is defined in scheme_classes.py. It would be very helpful to review it because you need to initialize and return an object from it.

    Hint: LambdaProcedure Class

    The code you write for this problem should be a single line long

    For more help

    Problem 8: Making New Frames

    In this problem we will be dealing with frames and the creation thereof. When we make a frame, we are initializing an object of class Frame. Each frame has bindings (a dict representing all the variables and values within a frame) and parent (the frame from which the new frame comes from).

    In Problem 1 we made the define() method that allowed us to define variables by saving them into the instance variable bindings. ! Remember this !

    For Problem 8, we are implementing the class method make_child_frame() which takes in two arguments: formals and vals.

    formals is a list of the symbols (the variables to which we are assigning values)

    vals is a list of the values we are assigning to the symbols in formals

    Scheme

    (define a 1)

    (define b 2)

    (define c 3)

    Python

    formals = Pair(‘a’, Pair(‘b’, Pair(‘c’, nil)))

    vals = Pair(‘a’, Pair(‘b’, Pair(‘c’, nil)))

    The CS 111 website gives four steps which are decently clear and pretty much make up the pseudo code for this problem:

    Hint: Assigning the Values

    Hint: Raise Error Syntax

    For more help

    For even more help

    And for even more, but less related help

    Problem 9: Lambda meets Frame

    This is another problem that should not require a lot of code, only two lines to be exact. In scheme_eval_apply.py we have the scheme_apply() function that applies procedures to arguments.

    scheme_apply() takes in procedure, args, and env. procedure is going to be an object of class Procedure. It could be a BuiltinProcedure or a LambdaProcedure (MuProcedure or MacroProcedure later too).

    procedure = LambdaProcedure(operators, operations, env)

    LambdaProcedure(Pair(‘x’, nil), Pair(Pair(‘*’, Pair(‘x’, Pair(‘x’, nil))), nil), <Global Frame>)

    args stores the values of the arguments that our procedure is going to be applied to. In the above box it would be the value that replaces the x’s in the LambdaProcedure.

    Scheme

    (define square (lambda (x) (* x x)))

    (square 21)

    Python

    args = Pair(21, nil)

    When we make a call to a lambda function, it opens a new frame in which the operators are defined and the operations take place. That means you need to make a new frame whose parent is the current frame. (First Line) Then you need to return an evaluation of all the body of the lambda function (the operations part) using eval_all(). (Second Line)

    Hint: SchemeError: unknown identifier: y

    Hint: Making New Frames

    Unfortunately there is no more help ):

    Problem 10: Defining Func’s Shorthandedly

    In Problem 10 we are going to edit the do_define_form() function that we wrote a part of in Problem 4. In Problem 4 we made it so that we could assign values to a variable name. Now we want to make it so we can assign functions to a variable name using the following syntax in Scheme:

    scm> (define (square x) (* x x))
    square
    scm> (square 2)
    4

    do_define_form() takes two arguments: expressions and env. expressions, as normal, is a list of objects from the class Pair. Consider the following example:

    Scheme

    (define (f x y) (+ x y))

    Python

    expressions =

    Pair(Pair(‘f’, Pair(‘x’, Pair(‘y’, nil))), Pair(Pair(‘+’, Pair(‘x’, Pair(‘y’, nil))), nil))

    You need to dissect expressions to get the function name, the operators, and the operations. Keep in mind we have the variable signature which is equal to the red highlighted portion of expressions above. To define the function we need to take the variable name and assign an object of class LambdaProcedure to it. To create this object we are going to use the function do_lambda_form() from Problem 7.

    As a reminder, do_lambda_form() takes in two arguments: expressions and env. It returns an object of class LambdaProcedure. expressions must be a scheme list represented by Pair class objects that hold the operators and the operations. You must create this Pair object in which the first part is the operators and the rest is the operations.

    Once you have the LambdaProcedure object, assign it to the variable name in the current frame.

    For more help

    Problem 11: The ‘Mu’

    The first important thing is to understand the difference between lexical and dynamic scoping. Here is the explanation again from the CS 111 website:

    CS 111 Website Description

    First, implement do_mu_form() in scheme_forms.py so that it returns an object of class MuProcedure. This should probably only be one line long. If you need help just look at your code for Problem 7. We are essentially doing the same thing except we are ignoring env because when initializing a MuProcedure we ignore the current frame because of dynamic scoping.

    Make sure to look at the __init__() for MuProcedure to see what it intakes as arguments:

    The class MuProcedure

    Here are what the arguments to the __init__() would look like if we were initializing a MuProcedure object based off of the given scheme expression:

    Scheme

    (define f (mu (x) (+ x y)))

    Python

    formals = Pair(‘x’, nil) #variable name

    body = Pair(Pair(‘+’, Pair(‘x’, Pair(‘y’, nil))), nil) #operations

    Second, you need to edit the scheme_apply() function in scheme_eval_apply.py for when the procedure is a MuProcedure. Return the evaluation of the body of the MuProcedure object, but remember you have to do that using the correct frame that results in dynamic scoping. So beforehand make a new frame based off of the current frame. Use this new frame when you evaluate the MuProcedure.

    Hint: Evaluating a Procedure

    Hint: Creating a New Frame

    For more help

    </end>

    Hopefully this was a sufficient help. Please submit any corrections or questions in the comments below. I will try my best to answer and update the article accordingly.

    Part 3