The Little Schemer: What is a function or argument's structure? - scheme

In Chapter 3 of The Little Schemer, the answer to the question of why we don't simplify the rember function right away is "because then a function's structure does not coincide with its argument's structure." I'm having trouble understanding what a function's structure is, what an argument's structure is, and what the difference is between them.
Here's the unsimplified version:
(define rember
(lambda (a lat)
(cond
((null? lat) (quote ()))
(else (cond
(( eq? (car lat) a) (cdr lat))
(else (cons (car lat)
(rember a
( cdr lat)))))))))
And here's the simplified:
(define rember
(lambda (a lat)
(cond
((null? lat) (quote ()))
((eq? (car lat) a) (cdr lat))
(else (cons (car lat)
(rember a (cdr lat)))))))
From what I can tell, the main difference is that the function has gone from two conds asking one question each to one cond asking two questions.
The function's arguments are the atom "a" and the list "lat."
This is the first time, outside of the densely written foreword, where the book references the word "structure." In my mind, the definition of the word "structure" so far is open to interpretation.
Someone has asked this exact question here before, but I have trouble following the answer. Why does a two-cond structure coincide or not coincide with the structure of a list? A list, in my mind, doesn't have any conditions at all!
Isn't a condition equivalent to a question in Scheme? Perhaps I'm misunderstanding what a condition is, which could be a reasonable root of my frustration. Anyways, any clarification on this would be very much appreciated! Thank you!

Here for “structure of function” the author probably means that the body of the function, that is the condition:
(cond
((null? lat) ...)
(else ... (cond... (car lat) ... (cdr lat) ...)))
patterns exactly the “recursive” definition of a list, as either:
an empty list, or
a value with at least one element (the car), and a list (the cdr).
The new definition instead “folds” the two cond inside a single one, so that the “structure” of the function (the structure of the cond) does not reflect any more the “structure” of its list argument.

List is a type that could have been defined, with some pseudocode, as
(define-variant-record list
( () ) ; '() is a list
((hd . tl) ; a cons pair (with car field named `hd` and cdr `tl`)
(list tl)) ) ; is a list, if `tl` itself is a list
Then, it could be handled with a hypothetical (cf. EOPL) patern-matching construct variant-case:
(define rember
(lambda (a lat) ; an atom and a list of atoms
(variant-case (lat) ; follow the `lat` --
( () ; an empty list case, or
(quote ()))
( (hd . tl) ; a non-empty list, with car field `hd` and cdr `tl`
(cond
(( eq? hd a) tl)
(else
(cons hd
(rember a tl))))))))
which, by way of using the variant-case belonging to the data-type definition of list, naturally and visibly follows its structure (i.e. the two cases of its definition).
The first formulation (with the nested conds) just emulates the non-existent variant-case with the explicit access to the concrete data-type implementation, with the cars and the cdrs as it does.
The inner cond does not belong to this, and just deals with the specifics of rember on its own. That's why mashing the two conds into one may be seen as mixing the non-related concerns into one mish-mash (generally speaking; although here both are extremely simple and clear).

Related

Getting the ordered leaves of a tree in scheme

I'm going through an exercise to grab the 'leaves' of a nested list in scheme (from SICP). Here is the exercise input-output:
(define x (list (lis 1 2) (list 3 4)))
(fringe x)
; (1 2 3 4)
(fringe (list x x))
; (1 2 3 4 1 2 3 4)
Now, I've come up with two answers for this: one recursive and one iterative. Here are my two implementations below:
(define (fr lst)
(cond ((null? lst) '())
((not (pair? (car lst))) (cons (car lst) (fr (cdr lst))))
(else (append (fr (car lst)) (fr (cdr lst))))))
(define (add-element-to-list lst elem)
(if (null? lst)
(list elem)
(cons (car lst) (add-element-to-list (cdr lst) elem))))
(define (fringe lst)
(define L '())
(define (iter lst)
(if (not (pair? (car lst)))
(set! L (add-element-to-list L (car lst))) ; update the list if it's a leaf
(iter (car lst))) ; otherwise recurse
(if (not (null? (cdr lst))) (iter (cdr lst))) ; and if we have a cdr, recurse on that
L
)
(iter lst)
)
(fringe x)
(fr x)
(fr (list x x))
(fringe (list x x))
; (1 2 3 4)
; (1 2 3 4)
; (1 2 3 4 1 2 3 4)
; (1 2 3 4 1 2 3 4)
; OK
The problem for me is, this exercise took me forever to figure out with a ton of head-bashing along the way (and it's still difficult for me to 'get it' as I write this up). Here are a few things I struggled with and seeing if there are any suggestions on ways to deal with these issues in scheme:
I thought initially that there are two cases. The normal/scalar case and the nested case. However, it seems like there are actually three! There's the normal case, the nested case, and then the null case -- and inner-lists also have the null case! Is there a good general pattern or something to account for the null case? Is this something that comes up a lot?
In the iterative case, why do I have to return L at the end? Why doesn't (iter lst) just return that (i.e., if I removed the standalone-L at the bottom of the iter function).
Finally, is there a 'cleaner' way to implement the iterative case? It seems like I have so much code, where it could probably be improved on.
The reason there are three cases is that you are importing some scalar / vector distinction from some other language: Scheme doesn't have it and it is not helpful. Instead a list is a recursively-defined object: a list is either the empty list, or it is a pair of something and a list. That means there are two distinctions to make, not one: is an object a pair, and is an object the empty list:
(define (lyst? o)
(or (null? o)
(and (pair? o) (lyst? (cdr o)))))
That's completely different than a vector / scalar distinction. I don't know what language you're getting this from, but just think about how the maths of this would work: vectors are defined over some scalar field, and there is no vector which is also a scalar. But for lists there is a list which is not a pair. Just stop thinking about vectors and scalars: it is not a helpful way to think about lists, pairs and the empty list.
The iterative version is too horrible to think about: there's a reason why SICP hasn't introduced set! yet.
First of all it's not actually iterative: like most of the 'iterative' solutions to this problem on the net it looks as if it is, but it's not. The reason it's not is that the skeleton of the iter function looks like
if blah
recurse on the first element of the list
otherwise do something else
if other blah
iterate on the rest of the list
And the critical thing is that both (1) and (2) always happen, so the call into the car of the list is not a tail call: it's a fully-fledged recursive call.
That being said you can make this much better: the absolutely standard way of doing this sort of thing is to use an accumulator:
(define (fringe l)
(define (fringe-loop thing accum)
(cond
((null? thing)
;; we're at the end of the list or an element which is empty list
accum)
((pair? thing)
;; we need to look at both the first of the list and the rest of the list
;; Note that the order is rest then first which means the accumulator
;; comes back in a good order
(fringe-loop (car thing)
(fringe-loop (cdr thing) accum)))
(else
;; not a list at all: collect this "atomic" thing
(cons thing accum))))
(fringe-loop l '()))
Note that this builds the fringe (linear) list from the bottom up, which is the natural way of building linear lists with recursion. To achieve this it slightly deviously orders the way it looks at things so the results come out in the right order. Note also that this is also not iterative: it's recursive, because of the (fringe-loop ... (fringe-loop ...)) call. But this time that's much clearer.
The reason it's not iterative is that the process of searching a (tree-like, Lisp) list is not iterative: it's what SICP would call a 'recursive process' because (Lisp's tree-like) lists are recursively defined in both their first and rest field. Nothing you can do will make the process iterative.
But you can make the code to appear iterative at the implementation level by managing the stack explicitly thus turning it into a tail recursive version. The nature of the computational process doesn't change though:
(define (fringe l)
(define (fringe-loop thing accum stack)
(cond
((null? thing)
;; ignore the () sentinel or () element
(if (null? stack)
;; nothing more to do
accum
;; continue with the thing most recently put aside
(fringe-loop (car stack) accum (cdr stack))))
((pair? thing)
;; carry on to the right, remembering to look to the left later
(fringe-loop (cdr thing) accum (cons (car thing) stack)))
(else
;; we're going to collect this atomic thing but we also need
;; to check the stack
(if (null? stack)
;; we're done
(cons thing accum)
;; collect this and continue with what was put aside
(fringe-loop (car stack) (cons thing accum) (cdr stack))))))
(fringe-loop l '() '()))
Whether that's worth it depends on how expensive you think recursive calls are and whether there is any recursion limit. However the general trick of explicitly managing what you are going to do next is useful in general as it can make it much easier to control search order.
(Note, of course, that you can do a trick like this for any program at all!)
It's about types. Principled development follows types. Then it becomes easy.
Lisp is an untyped language. It's like assembler on steroids. There are no types, no constraints on what you're able to code.
There are no types enforced by the language, but still there are types, conceptually. We code to types, we handle types, we produce values to a given specs i.e. values of some types as needed for the pieces of bigger system to interface properly, for the functions we write to work together properly, etc. etc.
What is it we're building a fringe of? Is it a "list"?
What is a "list"? Is it
(define (list? ls)
(or (null? ls)
(and (pair? ls)
(list? (cdr ls)))))
Is this what we're building a fringe of? How come it says nothing about the car of the thing, are we to ignore anything that's in the car? Why, no, of course not. We're not transforming a list. We're actually transforming a tree:
(define (tree? ls)
(or (null? ls)
(and (pair? ls)
(tree? (car ls))
(tree? (cdr ls)))))
Is it really enough though to only be able to have ()s in it? Probably not.
Is it
(define (tree? ls)
(or (null? ls)
(not (pair? ls)) ;; (atom? ls) is what we mean
(and ;; (pair? ls)
(tree? (car ls))
(tree? (cdr ls)))))
It 1 a tree? Apparently it is, but let's put this aside for now.
What we have here, is a structured, principled way to see a piece of data as belonging to a certain type. Or as some say, data type.
So then we just follow the same skeleton of the data type definition / predicate, to write a function that is to process the values of said type in some specific way (this is the approach promoted by Sterling and Shapiro's "The Art of Prolog").
(define (tree-fringe ls)
So, what is it to produce? A list of atoms in its leaves, that's what.
(cond
((null? ls)
A () is already a list?.
ls)
((not (pair? ls)) ;; (atom? ls) is what we mean
(handle-atom-case ls))
Let's put this off for now. On to the next case,
(else
;; (tree? (car ls))
;; (tree? (cdr ls))
both car and cdr of ls are tree?s. How to handle them, we already know. It's
(let ((a (tree-fringe (car ls)))
(b (tree-fringe (cdr ls)))
and what do we do with the two pieces? We piece them together. First goes the fringe from the left, then from the right. Simple:
(append a b )))))
(define (handle-atom-case ls)
;; bad name, inline its code inside
;; the `tree-fringe` later, when we have it
And so, what type of data does append expect in both its arguments? A list?, again.
And this is what we must produce for an atomic "tree". Such "tree" is its own fringe. Except,
;; tree: 1 2
;; fringe: ( 1 ) ( 2 )
it must be a list?. It's actually quite simple to turn an atomic piece of data, any data, into a list? containing that piece of data.
........ )
And that was the only non-trivial thing we had to come up with here, to get to the solution.
Recursion is about breaking stuff apart into the sub-parts which are similar to the whole thing, transforming those with that same procedure we are trying to write, then combining the results in some simple and straightforward way.
If a tree? contains two smaller trees?, well, we've hit the jackpot -- we already know how to handle them!
And when we have structural data types, we already have the way to pick them apart. It is how they are defined anyway.
Maybe I'll address your second question later.

Need a hint on a homework question which asks to add numbers in a list in a particular way

This is a homework question
The function takes in a list as the parameter, which may contain as many layers as sublists as needed For example, '(a (1 b 3)) or '((a 3 5) (b (3 1) 4)). The output has the same list structure of the input (meaning that sublists are maintained), but the car of each list is the sum of all numbers in the list. And all other non-numeric values are discarded. As an example output, consider '((a 3 5) (b (3 1) 4)), the output should be '(16 (8) (8 (4))). Also, only use basic scheme instructions/operations such as + - * /, car, cdr, cons, append, null?, number?, if/else, cond, etc.. Cannot Use a helper method.
So far this is the code I have, which sometimes partially does the job. But I'm having a really hard time figuring out how to get the sum from the sublists to add up at one spot at the car of the outmost list.
(define partialsums*
(lambda (lis)
(cond
[(null? lis) '(0)]
[(list? (car lis)) (cons (partialsums* (car lis)) (if (not (null? (cdr lis))) (partialsums* (cdr lis)) '()))]
[(number? (car lis)) (cons (+ (car lis) (car (partialsums* (cdr lis)))) '())]
[else (cons (+ 0 (car (partialsums* (cdr lis)))) '())])))
I've already spent several hours on this but couldn't quite grasp how to correctly approach the problem, probably because this is my first week using scheme :(. Any help is appreciated.
Also, I cannot use a helper method. Everything needs to be done inside one function in a recursive style. letrec is not allowed either.
To make life easy, you should model the data. Since there are no types, we can do this informally.
What is the structure of the input?
We can model it like "Data Definitions" from How to Design Programs. Read the "Intertwined Data" section because our data definition is similar to that of an S-expression.
; A NestedElem is one of:
; - Atom
; - NestedList
; An Atom is one of:
; - Number
; - Symbol
; A NestedList is one of
; - '()
; - (cons NestedElem NestedList)
We can define an atom? predicate to help us differentiate between clauses of the kinds of data in our program.
; Any -> Boolean
; is `a` an atom?
(define atom?
(lambda (a)
(or (number? a)
(symbol? a))))
The structure of the program should match the structure of the Data.
So we define a "template" on our data. It distinguished and destructs each data into clauses. It further de-structures the rhs of a clause.
; NestedElem -> ...
(define nested-elem-template
(lambda (ne)
(cond
[(atom? ne) ...]
[else ...])))
; Atom -> ...
(define atom-template
(lambda (atom)
(cond [(number? atom) ...]
[(symbol? atom) ...])))
; NestedList -> ...
(define nested-list-template
(lambda (nl)
(cond [(null? nl) ...]
[else (... (car nl)... (cdr nl))])))
We definitely know more about the data. (car nl) in the nested-list-template is of type NestedElem. Therefore we can fill up some ...s with calls to templates that deal with that kind of data. In the same vein, we can wrap recursive calls around expressions of a datatype we know of.
; NestedElem -> ...
(define nested-elem-template
(lambda (ne)
(cond
[(atom? ne) (atom-template ne)]
[else (nested-list-template ne)])))
; Atom -> ...
(define atom-template
(lambda (atom)
(cond [(number? atom) ...]
[(symbol? atom) ...])))
; NestedList -> ...
(define nested-list-template
(lambda (nl)
(cond [(null? nl) ...]
[else (... (nested-elem-template (car nl))
... (nested-list-template (cdr nl)))])))
Now we can "fill in the blanks".
We can "filter", "map", and "fold" over this data structure. All those can be defined using the template as a scaffold.
Note 1: Your HW asks you to do multiple tasks:
remove the symbols
sum up the numbers
cons the sum onto every list
Don't try to do everything in a single function. Delegate into multiple helper functions/traversals.
Note 2: I did not model the output type. It's the same as input type except that Symbol is no longer an atom.

How can I pull and return variables from a list?

I'm working on a project and want to create a function that will take a logical proposition in the form of a list as input and return a new list consisting of the variable names.
For example:
(A and (not B or C)) would return (A B C)
But I'm having a hard time with looping through the input list, especially when it involves nested lists like in the example.
edit: Thanks, got some code that works:
(define (flatten list)
(cond ((null? list) '())
((pair? (car list))
(append (flatten (car list))
(flatten (cdr list))))
(else (cons (car list) (flatten (cdr list))))))
(define (remove-element list)
(filter (lambda (x)
(and (and (and (not (equal? x 'and))
(not (equal? x 'or)))
(and (not (equal? x 'implies))
(not (equal? x 'not))) )
(not (equal? x 'iff))))
(flatten list)))
A typical beginner assignment is to flatten a list. You'll find lots of questions here about that and basically it will make '(A and (not B or C)) into (A and not B or C). Then you're almost there.
Basically there is no distinction between a variable and an operator since eg. not can come before and and can come between. I guess you cannot have variable names that are the same as your operators and you need to know the operators in advance. Then you can filter the flattened list to remove the operators. you'll then be left with (A B C).
It's difficult helping with specifics when you don't even have any code in your question so this is a s far as I can help you. Good luck.

The Little Schemer: `lat?` without `cond`?

One of the first questions in the second chapter of The Little Schemer (4th edition) asks the reader to write the function lat?, where (lat? l) returns true if l is a list of atoms.
It goes on to say:
You were not expected to be able to do this yet, because you are still missing some ingredients.
But I'm familiar with recursion, and the definition of atom? earlier in the book already introduced and (further implying the existence of or), so I gave it a shot anyway: (repl)
(define lat?
(lambda (l)
(or
(null? l)
(and
(atom? (car l))
(lat? (cdr l))))))
On the next page, the book introduces the cond operator to enable this definition of lat?:
(define lat?
(lambda (l)
(cond
((null? l) #t)
((atom? (car l)) (lat? (cdr l)))
(else #f))))
Is there any significant difference between these two implementations?
cond is a special form, that takes (roughly) the form
(cond
((test-expression) (then-expression))
((test-expression2) (then-expression2))
(else
(then-expression3)))
Its semantics is that it will evaluate the test-expressions in order, and for the first one that it finds to evaluate to #t (the true value), then it will evaluate its associated then-expression and return its value. If all the test-expressions evaluate to #f (the false value), and an else clause is present, then it will evaluate its associated then-expression3 in this case, and return its value.
So as far as semantics are concerned, the two implementations are equivalent. Their only difference might be that afaik the cond version is considered more idiomatic in the Scheme community.

I have got this code to remove the last element of a list. How can i change this to remove a set word regardless of its location?

#lang racket
(define (remove-last lst)
(if (null? (cdr lst))
'()
(cons (car lst) (remove-last (cdr lst)))))
(remove-last '(Help Me Please))
This then prints out:
(Help Me)
How can I change this? For example if I wanted to remove me.
Like this, for example:
(define (remove-words lst words)
(cond
((null? lst)
'())
((member (car lst) words)
(remove-words (cdr lst) words))
(else
(cons (car lst) (remove-words (cdr lst) words)))))
then
> (remove-words '(Help Me Please) '(Me Help Not))
'(Please)
You can also use the procedures for sets:
(define (remove-words lst words)
(set-subtract lst words))
Please note that you are working with symbols here, not strings.
You can also solve the problem using filter-not and member together:
(define (remove-words lst words)
(filter-not (lambda (x) (member x words) lst))
If you want to cut down on the wordiness of that anonymous function, the tools most suited to that are curry and cut (with the latter needing a (require srfi/26) to use).
Currying a function turns it into a new function that accepts one argument, then returns another function that accepts one more argument, then again, then again, and so on until it has all the arguments it needs to call the original function. A curried version of remove-words would be called as ((remove-words lst) words) instead, and you could make it from our current implementation with (curry remove-words). This requires that the arguments be supplied in left to right order, which doesn't work for member. There's another form curryr that goes right-to-left, but because member takes an optional third argument it won't work.
You could use cut (from srfi 26) then, which lets you pick which arguments of a function you want to "lock-in" and which you want the new function to accept. (cut member <> words)creates a new function (lambda (arg) (member arg words)) and (cut * <> <> 8) creates (lambda (arg1 arg2) (* arg1 arg2 8)). So with this, remove-words looks like:
(require srfi/26)
(define (remove-words lst words)
(filter-not (cut member <> words) lst))
Although going with set-subtract is still probably the best solution since you should avoid reinventing the wheel as much as possible (unless you're trying to learn more about wheels). Nonetheless, it's very useful to have a firm grip on the general functions provided by Racket that make your life easier.

Resources