On this page:
Strings in Lines
Strings to Words
6.10

Lab 11: Chatting with Words

Implement this lab with the Beginning Student Language. Require the HtDP2e image and universe libraries at the top of your definitions:
(require 2htdp/image)
(require 2htdp/universe)

Make sure you follow The Style we use for the {B,I,A}SL{,+} languages in this class.

Open your current ChatClient implementation from labs 6-8, 10. Make sure you’ve completed these labs before you continue with this lab and save/submit your definitions. We will be extending this program in future labs.

Choose the initial Head and Hands, and get started!

Strings in Lines

Our current ChatClient has three parts: the client’s String name, the client’s Textbox, and the client’s Image chat history. An Image chat history poses a number of difficulties. How can we search for messages with certain words in them? How should we render the history if the size of the chat window changes? We can solve these problems and more by changing the representation of the history to a ListofMessage.

Ex 1: Design the data definition ListofMessage that can hold arbitrarily many Messages. Use cons and '() in your implementation of ListofMessage.

Ex 2: Write down the template msg-list-template : ListofMessage -> ??? for functions that operate on ListofMessages.

But we’re not done yet. To change the meaning of History we have to

Ex 3: Update the data definition History to be defined as some ListofMessage.

But we’ve got a problem. All the functions we defined that expect or produce values of type History no longer satisfy the specification. They expect or produce Images. We need to fix that.

As some of you discovered when defining the original add-message, there are a number of interesting edge-cases when rendering Strings as Images, one of the trickiest being how to split long content into multiple lines to fit the window. We’ll need to split a Message into multiple lines of Strings.

Ex 4: Design the data definition ListofString that can hold arbitrarily many Strings. We suggest you use cons and '() in your implementation of ListofString; but feel free to roll-your-own structures if you’d prefer.

Ex 5: Write down the template str-list-template : ListofString -> ??? for functions that operate on ListofStrings.

Ex 6: Define the function string->lines : String Natural -> ListofString, that given a String and a maximum character width for each line of text, returns correctly ordered substrings of the input as a ListofStrings.

For example:

> (string->lines "How many lines will this be?" 10)

(cons "How many l" (cons "ines will " (cons "this be?" empty)))

The function string->lines breaks up strings to arbitrary character lengths. This has the undesirable feature of splitting some words into multiple lines, like the word "lines" in the above example.

Instead, we should represent lines as a series of words.

Turning a string into a list words is trickier than it sounds. We need to identify whitespace in the string, remove it, and stitch the other characters together into words.

Strings to Words

Swap Head and Hands!

Note: To help us design the function string->words, consider the following data definitions that distinguish two kinds of Chars.

; A WhiteSpace is a Char for which char-whitespace? returns #true.
; A WordChar is a Char for which char-whitespace? returns #false.

Ex 7: Taken together, do the data definitions WhiteSpace and WordChar describe the same set of values as Char? If so, what’s the point of giving the new data definitions? There are at least two good reasons.

One of those reasons is that we can use these data definitions to define other data definitions, such as Words:

; A Word is a String where each character is a WordChar.

Ex 8: Calling string->list on a String returns a ListofChar. Can you name a more specific data definition for values that string->list returns given a Word?

Now we can give you the following data definitions for ListofChar and ListofWord, and use them to define a function that groups a single String into a ListofWords. The helper function add-char-to-first-word strays a bit from the templates you’ve seen; we’ll discuss templates for functions over multiple arguments in an upcoming lecture.

For now, copy the following into your definitions window.

; A ListofChar is one of:
; - '()
; - (cons WhiteSpace ListofChar)
; - (cons WordChar ListofChar)
; 
; char-list-template : ListofChar -> ???
(define (char-list-template cs)
  (cond [(empty? cs) ...]
        [else (... (first cs)
                   ...
                   (char-list-template (rest cs)))]))
 
; A ListofWord is one of:
; - '()
; - (cons Word ListofWord)
; 
; word-list-template : ListofWord -> ???
(define (word-list-template ws)
  (cond [(empty? ws) ...]
        [else (... (first ws)
                   ...
                   (word-list-template (rest ws)))]))
 
; add-char-to-first-word : Char ListofWord -> ListofWord
; Add the given Char to the first Word in the given list, if it is a
; WordChar. If it is WhiteSpace, add the empty Word to the front of
; the result.
(define (add-char-to-first-word c ws)
  (cond [(char-whitespace? c) (cons "" ws)]
        [(empty? ws) (cons (string c) empty)]
        [else (cons (string-append (string c) (car ws)) (rest ws))]))
 
(check-expect (add-char-to-first-word #\tab '()) (cons "" '()))
(check-expect (add-char-to-first-word #\f '()) (cons "f" '()))
(check-expect (add-char-to-first-word #\f (cons "" '())) (cons "f" '()))
(check-expect (add-char-to-first-word #\o (cons "f" '())) (cons "of" '()))
(check-expect (add-char-to-first-word #\space (cons "oof" '()))
              (cons "" (cons "oof" '())))
(check-expect (add-char-to-first-word #\b (cons "" (cons "oof" '())))
              (cons "b" (cons "oof" '())))
 
; chars->words : ListofChar -> ListofWord
; Group the given list of chars into a list of words in the reverse order.
(define (chars->words cs) '()) ; <- stub
 
(define foo-chars (cons #\f (cons #\o (cons #\o '()))))
(define bar-chars (cons #\b (cons #\a (cons #\r '()))))
(check-expect (chars->words '()) '())
(check-expect (chars->words foo-chars) (cons "foo" '()))
(check-expect (chars->words bar-chars) (cons "bar" '()))
(check-expect (chars->words (append foo-chars (cons #\space empty) bar-chars))
              (cons "foo" (cons "bar" '())))
 
; string->words : String -> ListofWord
; Group the given string into a list of words, delimited by whitespace.
(define (string->words s) '()) ; <- stub
 
(check-expect (string->words "") '())
(check-expect (string->words " ") (cons "" '()))
(check-expect (string->words "foo") (cons "foo" '()))
(check-expect (string->words "foo bar") (cons "foo" (cons "bar" '())))
(check-expect (string->words "foo  bar") (cons "foo" (cons "" (cons "bar" '()))))

We’ll do this in a few steps. We gave you the helper function add-char-to-first-word.

Ex 9: Define the function chars->words that, given a ListofChar, returns a ListofWord where the Chars of the input are grouped together into Words. Use the helper function add-char-to-first-word in your definition.

Ex 10: Define the function string->words that, given a String, returns a ListofWord. You may want to use the helper functions chars->words and string->list in your definition.

Ex 11: The function string->words sometimes includes the empty string in its output. Under what conditions does this happen? Does this break its signature?

Ex 12: Design the function remove-empties that, given a ListofWords, returns a ListofWords with no empty strings in it. Modify your definition of string->words so it does not ever include the empty String in its output.

Cool! In the next lab we’ll can break our Messages into multiple lines of Words, so we don’t garble the content in our ChatClient.