1. Define a function `zero?` that expects a single number as an argument, and returns `'true` if that number is `0`, else returns `'false`. Your solution should have a form something like this:
let
zero? match lambda x. FILL_IN_THIS_PART
in zero?
You can use the `if...then...else` construction if you like, but it will make it easier to generalize to later problems if you use the `case EXPRESSION of PATTERN1 then RESULT1; PATTERN2 then RESULT2; ... end` construction instead.
2. Define a function `empty?` that expects a sequence of values as an argument (doesn't matter what type of values), and returns `'true` if that sequence is the empty sequence `[]`, else returns `'false`. Here your solution should have a form something like this:
let
empty? match lambda xs. case xs of
FILL_IN_THIS_PART
end
in empty?
3. Define a function `tail` that expects a sequence of values as an argument (doesn't matter what type of values), and returns that sequence with the first element (if any) stripped away. (Applying `tail` to the empty sequence `[]` can just give us back the empty sequence.)
4. Define a function `drop` that expects two arguments, in the form (*number*, *sequence*), and works like this:
drop (0, [10, 20, 30]) # evaluates to [10, 20, 30]
drop (1, [10, 20, 30]) # evaluates to [20, 30]
drop (2, [10, 20, 30]) # evaluates to [30]
drop (3, [10, 20, 30]) # evaluates to []
drop (4, [10, 20, 30]) # evaluates to []
Your solution should have a form something like this:
letrec
drop match lambda (n, xs). FILL_IN_THIS_PART
in drop
What is the relation between `tail` and `drop`?
5. Define a function `take` that expects two arguments, in the same form as `drop`, but works like this instead:
take (0, [10, 20, 30]) # evaluates to []
take (1, [10, 20, 30]) # evaluates to [10]
take (2, [10, 20, 30]) # evaluates to [10, 20]
take (3, [10, 20, 30]) # evaluates to [10, 20, 30]
take (4, [10, 20, 30]) # evaluates to [10, 20, 30]
6. Define a function `split` that expects two arguments, in the same form as `drop` and `take`, but this time evaluates to a pair of results. It works like this:
split (0, [10, 20, 30]) # evaluates to ([], [10, 20, 30])
split (1, [10, 20, 30]) # evaluates to ([10], [20, 30])
split (2, [10, 20, 30]) # evaluates to ([10, 20], [30])
split (3, [10, 20, 30]) # evaluates to ([10, 20, 30], [])
split (4, [10, 20, 30]) # evaluates to ([10, 20, 30], [])
Here's a way to answer this problem making use of your answers to previous questions:
letrec
drop match ... ; # as in problem 4
take match ... ; # as in problem 5
split match lambda (n, xs). let
ys = take (n, xs);
zs = drop (n, xs)
in (ys, zs)
in split
However, we want you to instead write this function from scratch. The method displayed above would traverse the sequence (at least, its first `n` members) *twice*. Can you do it in a way that only traverses (that part of the) sequence once?
7. Write a function `filter` that expects two arguments. The second argument will be a sequence `xs` with elements of some type *t*, for example numbers. The first argument will be a function `p` that itself expects arguments of type *t* and returns `'true` or `'false`. What `filter` should return is a sequence that contains exactly those members of `xs` for which `p` returned `'true`. For example, helping ourself to a function `odd?` that works as you'd expect:
filter (odd?, [11, 12, 13, 14]) # evaluates to [11, 13]
filter (odd?, [11]) # evaluates to [11]
filter (odd?, [12, 14]) # evaluates to []
8. Write a function `partition` that expects two arguments, in the same form as `filter`, but this time evaluates to a pair of results. It works like this:
partition (odd?, [11, 12, 13, 14]) # evaluates to ([11, 13], [12, 14])
partition (odd?, [11]) # evaluates to ([11], [])
partition (odd?, [12, 14]) # evaluates to ([], [12, 14])
9. Write a function `double` that expects one argument which is a sequence of numbers, and returns a sequence of the same length with the corresponding elements each being twice the value of the original element. For example:
double [10, 20, 30] # evaluates to [20, 40, 60]
double [] # evaluates to []
10. Write a function `map` that generalizes `double`. This function expects a pair of arguments, the second being a sequence `xs` with elements of some type *t*, for example numbers. The first argument will be a function `f` that itself expects arguments of type *t* and returns some type *t'* of result. What `map` should return is a sequence of the results, in the same order as the corresponding original elements. The result should be that we could say:
letrec
map match lambda (f, xs). FILL_IN_THIS_PART;
double match lambda xs. map ((lambda x. 2*x), xs)
in ...
11. Write a function `map2` that generalizes `map`. This function expects a triple of arguments: the first being a function `f` as for `map`, and the second and third being two sequences. In this case `f` is a function that expects *two* arguments, one from the first of the sequences and the other from the corresponding position in the other sequence. The result should behave like this:
map2 ((lambda (x,y). 10*x + y), [1, 2, 3], [4, 5, 6]) # evaluates to [14, 25, 36]
###Extra credit problems###
* In class I mentioned a function `&&` which occupied the position *between* its arguments, rather than coming before them (this is called an "infix" function). The way that it works is that `[1, 2, 3] && [4, 5]` evaluates to `[1, 2, 3, 4, 5]`. Define this function, making use of `letrec` and the simpler infix operation `&`.
* Write a function `unmap2` that is something like the inverse of `map2`. This function expects two arguments, the second being a sequence of elements of some type *t*. The first is a function `g` that expects a single argument of type *t* and returns a *pair* of results, rather than just one result. We want to collate these results, the first into one sequence, and the second into a different sequence. Then `unmap2` should return those two sequences. Thus if:
g z1 # evaluates to (x1, y1)
g z2 # evaluates to (x2, y2)
g z3 # evaluates to (x3, y3)
Then `unmap2 (g, [z1, z2, z3])` should evaluate to `([x1, x2, x3], [y1, y2, y3])`.
* Write a function `takewhile` that expects a `p` argument like `filter`, and also a sequence. The result should behave like this:
takewhile ((lambda x. x < 10), [1, 2, 20, 4, 40]) # evaluates to [1, 2]
Note that we stop "taking" once we reach `20`, even though there are still later elements in the sequence that are less than `10`.
* Write a function `dropwhile` that expects a `p` argument like `filter`, and also a sequence. The result should behave like this:
dropwhile ((lambda x. x < 10), [1, 2, 20, 4, 40]) # evaluates to [20, 4, 40]
Note that we stop "dropping" once we reach `20`, even though there are still later elements in the sequence that are less than `10`.
* Write a function `reverse` that returns the reverse of a sequence. Thus, `reverse [1, 2, 3, 4]` should evaluate to `[4, 3, 2, 1]`.