Racket’s let and for

/ 教程, racket, english

updated at: 2022-02-25

Take a bite of racket’s Local Binding, at the first sight we might already acquire the gist about the usage of define. And then it will come to us with let and for, both of them can be used for loops.

    1 let

      1.1 use let* for processing with redefining variables

      1.2 use let for loops

      1.3 implement Ruby’s each_slice method with let

    2 for

      2.1 X-expression

      2.2 basic usage of X-expression

      2.3 generating X-expression with for/list

      2.4 rewrite the preceding example with for/fold

      2.5 comparing for/list with map, foldl with for/fold, for/last with findf

1  let🔗

In my view, when we are using define, mostly we are thinking over things from the middle part of process. To be specific, the fundamental define gives you the flexibility to use it whenever you need it, and no matter wherever you are at this moment. But with the thinking of using let/let*/letrec, it’s like you are thinking out of boxes and thinking before your next writting of block code. this is more like you’re imaging there exists a black box called let,and you’re going to use it to implement your next bunch of code.

The reason why I said this is because of my awareness of the flexiable and various usages of Racket’s let, and most of that has been shown as examples at Local Binding, and which usages you might notice it’s quite hard to be found out in other nowadays popular languages, at least not a more consice way.

The basic difference between let/let*/letrec is they do the binding parallelly, sequentially and recursively.

1.1  use let* for processing with redefining variables🔗

Let’s say we have a text love, and I need to write code to output it to %I ++love-- you | I ++love-- you%.

> (let* ([text "love"]
         [text (string-append "I ++" text "-- you")]
         [text (string-append  text "|" text)])
     (string-append "%" text "%"))

"%I ++love-- you|I ++love-- you%"

There is also another example I wrote for Yijing.

With Racket’s let*, we can redefining a variable with processes of changing its values again and again.

1.2  use let for loops🔗

Notably we know let can be used for local binding, but at the same time we are binding, we can involve a loop to it. This can be useful for a one-off process. Imagine you need to handle with a list of 1 8 3 0 4 8 3 0, if you encounter a 8, then add a x following it.

> (require racket/list)
> (let loop ([lst '(1 8 3 0 4 8 3 0)]
             [result '()])
    (if (empty? lst)
        result
        (begin
          (loop (cdr lst)
                (if (= (car lst) 8)
                  (append result '(8 x))
                  (append result (list (car lst))))))))

'(1 8 x 3 0 4 8 x 3 0)

There is also another example I wrote for Qweather.

I guess using the OOP language, we have to define a method for this if we want it be compact. While Racket’s "let" has the ability to directly bind everything inside it originally.

1.3  implement Ruby’s each_slice method with let🔗

We know in Ruby programming language, we can write code like [1, 2, 3, 4, 5, 6, 7].each_slice(3).to_a to split the original array to a new two dimensions array, and each element of the new array almost has a similar size: [[1, 2, 3], [4, 5, 6], [7]].

If we try to write this method in a Racket function, it can also be implemented by let:
> (require racket/list)
> (let loop ([lst '(1 2 3 4 5 6 7 8 9 10 1 12 13 14)]
             [result '()])
    (if (< (length lst) 3)
        (append result (list lst))
        (loop (drop lst 3)
              (append  result (list (take lst 3))))))

'((1 2 3) (4 5 6) (7 8 9) (10 1 12) (13 14))

And beside, there is somebody already implemented this function and named it as slice-at. Check my answer of it in stackoverflow here.

2  for🔗

Since lots of languages use for as a keyword for loops, it could be very easy for us to master the usage of Racket’s for, but how about foldl\foldr or even for/list for/fold? Let’s take some prctical examples.

Before we dive into the example part, let me introduce the related background a little bit.

2.1  X-expression🔗

In the Ruby On Rails world, at the very begnning few years of this framework, it’s very prevalent to write the frontend code with Haml\Slim instead of HTML, which HTML in its world is call ERB. Well the core spirit of this is embedding Ruby code to the HTML document and make the whole mixed code much more readable and writable, although nowadays people are trying to make the JS taking more spaces comparing to that time.

But as I think, no matter for now or before, they have the same common traits of manipulating the HTML document by a progromming language, the difference is the executable capability of JS in the browser, makes the JS working on this at another feasible level seemingly.

But what I want to mention here is, if you have the experience of using HAML, you will notice the obvious annoyance of how hard it is to modify the HTML code, which mostly comes from the part of generated HTML of embedded Ruby code in ERB/Haml/Slim.

And as for now, I would say, this kind of annoying things can be much more smooth in the Racket world because of the vast use of sexpr in its language design.

In the terms of the standing of HTML docuemnt in LISP world, it is called X-expression, which is implemented by the XML: Parsing and Writing package in Racket specifically. And our following examples are about having something to do with the X-expression.

2.2  basic usage of X-expression🔗

> (require xml)
> (xexpr->string
    '(p "do I love you?"))

"<p>do I love you?</p>"

> (xexpr->string
    '(p ((class "question"))
       "do I love you?"))

"<p class=\"question\">do I love you?</p>"

> (xexpr->string
  '(div ((class "main"))
    (ul ((class "questions"))
      (li "do I love Lily?")
      (li "do I love Lucy?"))))

"<div class=\"main\"><ul class=\"questions\"><li>do I love Lily?</li><li>do I love Lucy?</li></ul></div>"

2.3  generating X-expression with for/list🔗

Since literally Xexpr is just list, so we can use all kind functions of quasiquote\unquote\unquote-splicing for the generating of Xexpr.
> (require xml)
> (define girls
    (list "Lily" "Lucy"))
> (xexpr->string
  `(div ((class "main"))
    (ul ((class "questions"))
    ,@(for/list ([name girls])
      (list 'li "do I love " name "?")))))

"<div class=\"main\"><ul class=\"questions\"><li>do I love Lily?</li><li>do I love Lucy?</li></ul></div>"

2.4  rewrite the preceding example with for/fold🔗

> (require xml)
> (define girls
    (list "Lily" "Lucy"))
> (xexpr->string
  `(div ((class "main"))
    ,(for/fold ([acc '(((class "qustions")) ul)]
               #:result (reverse acc))
               ([name girls])
       (cons (list 'li "do I love " name "?") acc))))

"<div class=\"main\"><ul class=\"qustions\"><li>do I love Lily?</li><li>do I love Lucy?</li></ul></div>"

2.5  comparing for/list with map, foldl with for/fold, for/last with findf🔗

Those pair of functions basically have the same effects, except the fors are much more flexiable and the code of them are much more concise. Let’s take the for/last for example.
> (define cities
    '(("北京" . "101010100")
      ("郑州" . "101180101")
      ("上海" . "101020100")
      ("新郑" . "101180106")))
> (string-append
    (car (findf
           (lambda (i) (string=? "新郑" (car i)))
           cities))
    "市")

"新郑市"

> (for/last ([i cities]
            #:when (string=? "新郑" (car i)))
    (string-append (car i) "市"))

"新郑市"

As you can see, with for/last, since the logic deepth is flatten, it then becomes much more readable.

Further more, if you are curious about how to implement a generated HTML page with Racket, you can start it up from a project of mine called daily report.