"Getting It" (or, Making It "Click")

Does anyone know of a document/blog entry/etc. that compares programming in Racket to programming in a language like Go? I'm still trying to get the hang of how to think of the structure of applications in Racket and it's something to get the mind wrapped around...I mean, in (for example) Go, you start with some imports, a package name, a main(), and in main(), I can call another function that passes back the result (and maybe an error), then call another function, just keep passing the data through these functions or hand them off to another routine via a channel that maybe passes the result to a structure or through a network connection that was passed as an argument to it. You create various steps to operate on data that is passed around. I find I need to check data at some point, I can write another function and just insert it into the flow. Racket doesn't seem to quite do that, it's...operating on lists and handles data through recursion?

I suppose I'm looking for a way to model the flow of data in my head. I have read articles that describe Racket (and Lisps and Schemes) as being difficult until it just sort of..."clicks", then it's a joy to work with. It sounds like there's a hurdle to comprehend how to "get" it and experience in other languages can hinder that inflection point? I wasn't sure if I was just a little too dim to understand how to use the language but then I saw something about debugging in Racket where it's not quite as useful to use a traditional debugger with the traditional methods, instead using the REPL to test and check functions (please feel free to correct me if I'm wrong there...)

I wondered then if there's a good description of something to help get the in-head modeling of programs written in Racket described. Or if anyone can share some insight on this inflection point in learning the language, or if I'm off base on this impression?

5 Likes

In racket, you start with #lang racket and some requires, and then you write (module+ main ...), and in that main submodule you call some functions. Otherwise, it works just like the description you gave for Go.

For doing work in Racket, you don't at all need to starting thinking about lists, or recursion, or needing things to "click". You can just start writing some functions and some modules in the way you would write them elsewhere, and go from there.

For some examples of how to build up a relatively-small application that's similar to what you may have seen in Go, I would check out this tutorial: More: Systems Programming with Racket

3 Likes

It is difficult for me to understand your perspective/know what exactly you feel like you aren't getting / isn't clicking for you.
Mostly to me it seems as if you are overthinking things, because you have read a bunch of things about lisps that may have had an effect of over-mystifying the language, by making a bunch of theoretical statements about what a lisp is. I don't think those theoretical things are very important, more important is that you get a sense for the language by doing something practical with it.

I think the best thing you could do is to write a few simple programs that do things you find interesting, or alternatively explore example programs that are interesting to you and play around with those.

Repl is a good thing, but debuggers are too, DrRacket also has a debugger, using it to step through a program may also be a good way to get a better sense of how the language works.
Both have their advantages in different situations so learning how to use them both is recommended, but again the most important thing is to use the language, instead of thinking about it, or reading about it.

In my experience that is the only way to really get a feel for it and feel like it "clicks".

Racket has different data structures just like most other languages, lists are often used, but you still can use the language to do something simple like write a command line application, or a program that generates pictures, or other things and those may work without using lists explicitly.

A simple racket program just looks like this:

#lang racket

(module+ main
  (displayln "this is printed when the program is executed with 'racket filename.rkt'"))

Here is a program that downloads the racket svg logo:

#lang racket

(require net/url)

(define/contract (download-file uri save-as-path)
  (-> string? path-string? any/c)
  (call-with-output-file save-as-path
    (λ (out)
      (define in (get-pure-port (string->url uri)))
      (copy-port in out))
    #:exists 'replace))

(module+ main
  (download-file "https://racket-lang.org/img/racket-logo.svg" "racket-logo.svg"))

This is just a random example I pulled from my personal folder of simple examples.
(so far I haven't published these examples, because I didn't have the time to make something more out of them, then the simple experiments they are. Maybe I will do that eventually, or make blog posts out of them someday)

Also I think reading examples like those is only helpful in a limited way, writing small programs is what actually teaches you, how to find the right functions in the documentation, how things fit together, how you can structure things etc..

What are you interested in creating?
Maybe someone has already an application that does something like that, or something similar.
Or we can give hints what packages / libraries you could use.
I always find it way better to learn a language when I have a concrete project where I can use it.

4 Likes

@bsilver I'm glad you wrote the question here, too. :slight_smile:

I agree on Simon's term "over-mystifying". :smiley: I've used Racket for free time projects for about two years. I can't say whether Lisp has "clicked" for me, but I do enjoy working with Scheme and Racket.

Design-wise, I usually work bottom-up. I think about the data types I'm going to need and define structs for them. In the same module, I write functions that usually take an instance of this struct (or several instances), depending on what's practical. You can think of the struct accessor functions that Racket generates for a struct type as examples for such functions working on instances of the struct. Of course, you may want functions that take values of several struct types as arguments, as with objects in other languages.

What is somewhat different compared to more mainstream languages is functional programming (FP). You can program imperatively in Racket, and practically all programs will have some imperative code, for example to do I/O. In FP (and hence Racket) it's normal that functions take values and return newly-created values instead of changing values in-place. For example, if you want to "modify" a struct value, you can use struct-copy to create a new struct value that shares some of the fields of the original value.

Regarding recursion: I think it's certainly helpful to have experience with recursion and you'll most likely need it at some point. On the other hand, most of the time you use functions or macros that "have the recursion built in", for example the for macros or higher-order functions like map or filter.

By the way, most recursing functions roughly follow this pattern:

(define (my-function argument)
  (cond
    [base-case-condition? base-case-value-or-expression]
    [else some-expression-that-calls-my-function-with-a-reduced-value-of-argument]))

(Of course, in practice it can get much more complicated, but the above is a starting point.)

So you could say a Racket program is a bunch of modules (with each module being an "encapsulation unit", like modules in other languages) that define one or more struct types and operations on them. (Besides, not all types you'll be working on have to be struct types, you can also operate on lists or vectors directly. But often you'll want struct types for encapsulation.)

That said, if your program or part of it "naturally" is about modifying state (for example in GUI widgets), you can also use Racket's class system.

Disclaimer: Of course programs can get more complicated and need more tools than what I described above, but I hope what I wrote is some useful information for a start. Feel free to ask more questions. :slight_smile:

2 Likes

Tutorials and clases sometimes try to teach Racket and recursion at the same time, so the weight of recursion is more than in normal programs. Also, to simplify the language,, they only use lists.

For grouping a snall ammount of different kind of data that are related to something, it's better to use a struct than a list.

For grouping many similar things, it's better to use a list or a vector or a hash (or a set). It depends on the what information you have about them and what are you going to do. Do they have a clear key? Are you going to acces them randomly? Do you want to save memory sharing part of the storage of the collection with another similar collection?

For example, a "nomal" program to create 100 small of rectangles and calculate the total arae is:

#lang racket

(struct rectangle (height width color))

(define picture (for/list ([n (in-range 100)])
                  (rectangle (add1 (random 10))
                             (add1 (random 10))
                             (if (zero? (random 2)) 'red 'blue))))

(define total-area 0)

(for ([r (in-list picture)])
  ;TODO: Remove set!
  (set! total-area (+ total-area
                      (* (rectangle-height r)
                         (rectangle-height r)))))

(displayln total-area)

You can use a list to save the data of each rectangle, but it's better to use a struct.

It's possible to use recursion to crete the bunch of rectangles, but it's easier to use for/list.

I think a list is fine for this example, they are probably ordered and you want to draw them in order. It would be nice to hide the details of the creations of the bunch of rectangles in function, but that's optional.

To calculate the total area, you can use a for to iterate over the bunch of rectangles instead of recursion.

I'm using set! but it's much better to use for/sum instead. It's possible to use set but it's not very idiomatic. Also, the compiler is unhappy with set! and code wthout set! usually has more optimizations and is faster. Try to avoid set! if possible.

The calculation of the total area looks like a common enough thing you want to do in a longer program, and it's also quite clear and isoated, so I'd definetively move it to a function definition. It's also easier to test and debug.

1 Like

As others have said, it sounds like you're overthinking it. For example, here's a simple and complete Racket program that you can paste into, e.g. test.rkt and run from the command line using racket test.rkt (Note: Prior posters have been mentioning the use of (module+ main ...) but that isn't strictly necessary.)

#lang racket

(define (factorial n)
  (cond [(= n 0) 1]  ; factorial of 0 is 1 by definition
        [else (* n (factorial (- n 1)))])) ; e.g. in math, factorial(7) is (7 * factorial(6))

(displayln (factorial 5))  ; prints 120

Here's another example that does all the things you mentioned doing in Go. Again, you can copy/paste this into a .rkt file and run it from the command line:

#lang racket

; Example code responding to bsilver from the Racket Discourse,
; showing how Racket would do each of the things he mentioned doing in
; Go.  His requests are preceded with '...', mine are in {}


(require racket/async-channel) ; ...you start with some imports...

; ...a package name
; {You don't need that in Racket}

(define (main) ; ...a main()
  (define x (add1 7)) ; ...I can call another function that passes back the result
  (displayln x) ; ...then call another function
  (define y (* x 7)) ; ...just keep passing the data through these functions

  (define ch (make-async-channel)) ; {create a channel (well, an `async-channel`, since `channel` is synchronous)}
  (struct person (name age) #:transparent) ; {create that structure type you wanted, to be used later}


  (async-channel-put ch y) ; ...hand them off to another routine via a channel

  (thread
   (thunk
    ; {create another thread to listen on the channel (not part of
    ; what you mentioned in Go but makes a good demonstration)}

    (define age (sync ch)) ; {receive the value handed off through the channel}

    (person 'bob age) ; ...that maybe passes the result to a structure
    ; {the above line creates an instance of structure type 'person'
    ; using the passed-in value for age. Hopefully that's what you
    ; meant by 'passes the result to a structure'}

    (async-channel-put ch "Finished.") ; {tell the main thread that we're done}
    ))


  ; ...or through a network connection that was passed as an argument to it
  (define socket  (udp-open-socket))
  (define udpport (udp-bind! socket "127.0.0.1" 22871)) ; {listen to UDP port 22871 on interface 127.0.0.1}   

  (do-network-thing udpport y)

  (displayln (sync ch)) ; wait for a signal to come back on the channel, then clean up and finish
  (udp-close socket)
  )

(define (do-network-thing udpport val)
  ;...do something through a network connection that was passed as an argument to it.


  ; {I'm commenting this out because I don't want to actually deal with network code in an 
  ; example, but it would send the byte string "hello, world" to the specified host 
  ; machine/port}
  ;
  ; (udp-send-to socket target-host target-port #"hello, world")


  ; ...I need to check data at some point, I can write another function and just insert it into the flow.
  ;
  ; {Not sure what this means.  Maybe anonymous functions?  Racket's gotcha covered.
  (when (equal? 11 val) ; {check the data}
    ((lambda (v) (display "inside anonymous func, v is: ") (displayln v)) val)) ;{write another function, insert it into the flow}
  )

(main) ; {kick things off by calling main}

Andy Wingo, who maintains Guile, has blog posts comparing Racket's threads and synchronizable events (which are based on Concurrent ML) with Go. Note that the first post, concurrent ml versus go -- wingolog, ends with:

[edit: Sam Tobin-Hochstadt tells me I got it wrong and I believe him :slight_smile: In the meantime while I work out how I was wrong, examples are welcome!]

so do also read the follow-up, a new concurrent ml -- wingolog.

1 Like