I also think many of the ideas you described for Go are also applicable — structs, goroutines (basically threads in racket), channels.
it might be heavier than you’re hoping for, but Software Design for Flexibility by Hanson and Sussman is pretty good as an intro to typical FP design approaches, written in Scheme
Thanks, I’ll take a look at the book! Also, @samth - racket has channels for communication?
> I just got a copy of Racket Programming the Fun Way, so I’m hoping that will shed some light on things as I’m working through it. If I don’t confuse something, someone mentioned recently in an online meetup that “Racket the Fun Way” often was using a rather “mutating style”, which would be rather untypical for Racket and Scheme.
@all Let me know if I confuse something. Was this possibly about another book?
@bsilverstrim A few things I can think of: • My modules typically define one or more structs and functions that operate on the struct. Modules are my primary “encapsulation device.” • It’s typical not to mutate instances of struct
s, but rather return modified struct
s, see https://docs.racket-lang.org/reference/struct-copy.html#%28form._%28%28lib._racket%2Fprivate%2Fbase..rkt%29._struct-copy%29%29 . • The same goes for lists. By far the most list functions return new lists. • If I use mutation, I try to keep it local to a function, so the function looks like a pure function (i.e. without side effects) from the outside. • Where my program does I/O, I try to write most of my code in pure functions that are then easier to test. I put the I/O in other functions that call the pure functions. • My programs typically are functions calling other functions, very rarely mutating state. You can define functions on the module top-level, but especially for small helper functions that you use only in a singe function, it’s common to put them in the function where they’re needed.
yeah, in general maintaining a strict boundary between the well-behaved functional core and sources of mutation or side effects is a typical program structure
Note that conditionals in Racket/Scheme (if
, cond
, case
etc.) behave like functions, so you can use them as such. Think of the language only having if
expressions, but no if
statements (and correspondingly for other conditionals).
either by encapsulating them in something that behaves functionally on the outside or pushing them to the outer boundary
@thechairman Yes, much of what I wrote would make sense for programs in imperative languages, too.
Sounds like part of the philosophy is to not take a struct of data and alter information in it, but instead pass a copy of a struct, mutate-alter-change what is needed to be operated on, and return a new version back to a caller.
less playing with pass-by-reference, more pass-by-value.
Most Racket APIs don’t even alter data. You typically don’t make copies explicitly (not even in your own functions). Take for example cons
( https://docs.racket-lang.org/reference/pairs.html#%28def._%28%28quote._~23~25kernel%29._cons%29%29 ). You pass in two values and get out another. The original values aren’t changed: (define my-list '(1 2 3))
(define my-list-with-an-item
(cons 4 my-list))
my-list -> '(1 2 3)
my-list-with-an-item -> '(4 1 2 3)
Lists are implemented as singly-linked lists that share data if possible. For example, cons
doesn’t make a copy of my-list
, but creates a new node that contains the new value 4 and points to my-list
for the rest of the list.
That said there are some APIs (in the standard library and elsewhere) that do mutate data. But I encourage trying not to mutate at first.
The good thing is that if your data is immutable, you can pass it by reference internally (for performance) and still don’t have to worry about modifying it.
@samdphillips Exactly. :slightly_smiling_face:
@bsilverstrim For example, there are immutable and mutable hashes: https://docs.racket-lang.org/reference/hashtables.html