It’s important to distinguish between the formal idea of how the computation is done and the actual implementation. Conceptually references to the values are passed around - in practise the important values gets special treatment (fixnums, the empty list, booleans). This is just to avoid anyone falling into the trap of thinking the implementation of function calls is inefficient.
There is a lot of choice when choosing the representation of values, but you can get an idea from Peter Bex’s blog post on the representation used in Chicken Scheme: https://www.more-magic.net/posts/internals-data-representation.html
https://www.more-magic.net/posts/internals-data-representation.html
I’m trying to understand how match
works and the https://docs.racket-lang.org/reference/match.html#tocview_0:~:text=The%20last%20body%20in%20the%20matching,with%20respect%20to%20the%20match%20expression.\|reference says > The last _body_ in the matching clause is evaluated in tail position with respect to the https://docs.racket-lang.org/reference/match.html#%28form._%28%28lib._racket%2Fmatch..rkt%29._match%29%29\|match expression. Can someone please explain what this means? Does tail position here have to do something with tail recursion?
Think of it as the expression calculating the return value
Yes. It means a program like: (define (sum n [a 0])
(match n
[0 0]
[n (sum (- n 1) (+ n a))]))
will run using constant stack space. The recursive call to sum in the body of the last match clause is syntactically in tail position, so at runtime the call will be tail recursive.
(which could be a recursive call, yes)
hmm so does that means match
itself is tail recursive? i.e. if the stack blows up, it is the fault of some clause
, but not match
itself? Is my understanding correct?
Yes.
Hmm, how come streams take constant space?
A stream is a sequence of elements like a list is. However, in a stream the rest of list has not been computed yet, so it doesn’t take the space. A list is fully computed and so you must store each element of the list somewhere in memory. That’s why streams let you build “infinite sequences”. This wouldn’t be possible if you had to store each element in memory.
@xuxue has joined the channel
> (define \foo "bar")
> foo
"bar"
Hi, I guess Racket would treat identifiers start with \
the same with the one without \
, right?
I’m not sure whether is it safe to use them interchangeably
How come modulo only works on integers?
In python I can do 1.5%5 for example
My guess is it’s because "" is an escape character in strings (and symbols share a lot with strings). "\f" corresponds to no special escape sequence, so it is just interpreted as “f”. Just my guess though
Doesn’t look like http://codereview.stackexchange.com\|codereview.stackexchange.com has a very active racket community. I wrote my first macro while trying to understand for
and wanted some feedback; where’s a good place for that?
Should be relatively easy to write this one based on the builtin one. Remember to check negative numbers, exact quotients, zeros, and +inf.0 and +nan.0
But possibly it already exists in some library. @soegaard2?
I thought we had an flmod
somewhere, but I can’t it. How is it defined? Is 1.5 % 5 the same as (15%50)/10 ?
According to Stackoverflow:
(define (flmod x m)
(- x (* (floor (/ x m)) m))
There’s flmod
in r6rs though: https://docs.racket-lang.org/search/index.html?q=flmod
I feel stupid even asking this, but I’ve been staring at this code for past 15 minutes and can’t figure out what I’m doing wrong > (letrec ([my-even? (lambda (x) (if (zero? x) #t (not (my-odd? (- x 1)))))]
[my-odd? (lambda (x) (if (zero? x) #f (not (my-even? (- x 1)))))])
(my-even? 5))
#t
can someone point out what the bug is?
remove the not
s: x is even iff (x–1) is odd
and vice-versa
oh yeah dang how could I have even written that :face_palm:
Happens :upside_down_face:
Could you provide an example? In the past, I asked about how to use streams like ports to read in the preorder traversal of a tree, and I saw a great example by @sorawee as to how to use streams for functional programming. Are you saying that the stream (stream '(bob 2) '(rob 1) '(job 0) '(foo 0))
was not computed all at once in this example?
https://racket.slack.com/archives/C09L257PY/p1605678504289300
Not really sure how streams are implemented, but in this particular example, they at least seem to be computed all at once?
Also, how could one create “infinite” streams?
One wouldn’t be able to type them out like in this example …
Hmmmmmmmmmm
Streams like that do get computed all at once, but the real advantage is in defining your own streams with gen:stream
This lecture will give you an exact and step by step construction of a stream. (The entire course is really great — and really famous.) https://www.youtube.com/watch?v=a2Qt9uxhNSM
Here’s a small example stream in racket too: (require racket/stream)
(struct doubles (n)
#:methods gen:stream
[(define (stream-empty? stream) #f)
(define (stream-first stream) (doubles-n stream))
(define (stream-rest stream) (doubles (* 2 (doubles-n stream))))])
(for ([x (stream-take (doubles 1) 10)])
(displayln x))
Man, I wish I went to MIT lol
Ah, I didn’t know about gen:stream
at all. No wonder I was confused
Hm, I don’t know if going to MIT is too good of a thing. But I’m really happy to be able to read what they write and understand what they say. Because, indeed, a lot of good work has been done there for sure.
Don’t punish yourself. You’re probably so concentrated on the syntax that you forgot to consider the strategy itself. Once the syntax is very familiar to you, you’ll get back to the strategy. (Loved the icon!) :sun_with_face:
Yes, an infinite stream wouldn’t be typed out. We’d give a stream-constructor a rule to go on. Start with this and the next is given by some other rule. If this rule has no end — like “a natural number plus one” —, the stream becomes infinite. And there are ways to do this with the stuff you already know — lists, functions, lambda expressions. In that lecture, you’ll watch it all from scratch. And you’ll probably watch other lectures too and the entire course is really worth watching carefully. And if you get done with the course, reading the book will also be an amazing step and perhaps you’d intermix both things. (But if you haven’t read yet, HtDP should definitely be the first book. Perhaps even before watching this course completely. HtDP has changed my life. This book has a very deep message and it’s not easily grasped in the first sections. I read it with the objective of just acquiring some local vocabulary — I was having a hard time with Racket’s documentation. But by reading it, I realized the book was teaching me how to program and I didn’t know I didn’t know. So it was life changing. But after HtDP, Structure and Interpretation of Computer Programs is definitely a new challenge. But I do think that watching the course before reading the book is a good idea. Perhaps better still would be watching the lectures and reading the book, then continuing. Life is also full of steps forward and backwards and redesigning. Your best luck though is that you’re among people who love this stuff and they are exceptionally talented. I’m not one of them, for sure, but they are here everywhere.
@anything I was actually using this to test a metacircular interpreter I was writing so I was getting frustrated and thought the fault was with the interpreter :sweat_smile:
Thanks for the compliment :smile:
It seems you’re [not] using paredit-mode. I love it.
> Your best luck though is that you’re among people who love this stuff and they are exceptionally talented. I’m not one of them, for sure, but they are here everywhere Given that we are both here doing and discussing this stuff, even though we might not be exceptionally talented, I wager that we both love this stuff, even though we are not as talented as the people who made Racket and made it as big as it is today.
I was putting off HtDP since I was like “uhhhhh, I already know Racket…. reading books is long and painful”, but I think I understand what you mean now. I will definitely give it a go after I watch the lecture, when I have the time (I know I need to make the time). The next few months, I will be working with Haskell, which is supposedly a “pure” functional programming language, but I feel that the lessons from this lecture and HtDP will obviously apply there as well.
At the end of the day we are all suffering from the Dunning Kruger effect. Most human beings (including me) are not really experts at anything, but people tend to think they know more than they do, which I like to avoid. Thank you for your recommendations, Any. I certainly need to rethink my approach to learning. And yes, I am glad to have discovered the Racket community. They weren’t joking about “Vibrant Community”
Let me also say that it’s good to go with your intuition as well. If you think a book is not for you, might as well respect that. But perhaps like me, you had the impression that the book is kinda trivial, which it is not. It doesn’t seem a crime to also jump ahead in a book to see if it’s still all trivial and perhaps be humble to go back when we find it is not. HtDP isn’t really about Racket. It’s perhaps more about processes and it is definitely about a certain way of thinking that will definitely help you. But even it would turn out to be trivial, what’s the problem? Who hasn’t ever read a trivial book or a bad book? Sometimes bad books give us immense insight too. Sometimes a bad book leads into errors and then I spot the error and I try to clear it up and insight comes. I must thank the book for the error. A bad book is only bad for the lazy. But of course one shouldn’t read all bad books in the world because we’d end up just doing that. In a sentence, respect to your intuition and stay in control of your education.
I think I tried it at some point - but couldn’t get used to it.
I am happy with paren-mode.
I failed at first too. These days I only use two commands — those that bring stuff from the right into the current parentheses I’m in and those that shift stuff out of the parenthesis I’m in. (And this seems very useful in itself.) I also had to remap C-<right> and C-<left> because by default those do the operations above and I’m too used to use them to just navigate from s-expr to s-expr. I remapped those operations to M-/ (for enlarging the scope of my current parentheses [using here new words to describe the operation I described earler] and M- for the inverse operation. Just that made me love it. (Oh, I also use M-s to get rid of a parentheses. That’s basically what all I use. I try not to spend too much time learning to use tools because there’s too many ou there. Every once in a while, though, we feel we need to improve.)
I’m using paredit-mode even in the Racket REPL, where I think it’s also very useful.
Here’s basically all I do wrt paredit: ;; Please, autoload racket-mode
(autoload 'racket-mode "racket-mode" "My Racket REPL." t)
;; When we load racket-mode by Greg Hendershott we also enable
;; paredit-mode because that mode is so great. However, paredit-mode
;; doesn't work when we are in noweb-mode. So let's not turn it on
;; when we are in literate programming worlds --- our daily world.
(defun turn-on-paredit-only-outside-noweb-mode ()
(when (not (bound-and-true-p noweb-mode))
(paredit-mode))
t)
(add-hook 'racket-mode-hook 'turn-on-paredit-only-outside-noweb-mode)
(add-hook 'racket-repl-mode-hook 'paredit-mode)
;; Let's set up some handy keybindings
(eval-after-load 'paredit
'(progn
(define-key paredit-mode-map (kbd "<C-left>") 'paredit-backward)
(define-key paredit-mode-map (kbd "<C-right>") 'paredit-forward)
(define-key paredit-mode-map (kbd "M-/") 'paredit-forward-slurp-sexp)
(define-key paredit-mode-map (kbd "M-\\") 'paredit-forward-barf-sexp)))
(eval-after-load 'racket-repl
'(progn
;; In the REPL, I don't really ask for indentation via <tab>. At
;; the most, I'd press RET for indentation. So I would prefer
;; <tab> to expand names.
(define-key racket-repl-mode-map (kbd "<tab>") 'dabbrev-expand)))
;; Smart parentheses
(show-paren-mode)
(electric-pair-mode)
(autoload 'enable-paredit-mode "paredit"
"Turn on pseudo-structural editing of Lisp code."
t)
(add-hook 'emacs-lisp-mode-hook 'enable-paredit-mode)
(add-hook 'lisp-mode-hook 'enable-paredit-mode)
(add-hook 'scheme-mode-hook 'enable-paredit-mode)
Yeah, even certain theorems in mathematics that seem “trivial” lead to extraordinary outcomes and uses in reality, something like prime numbers and finding them for example, is very useful, even though it just seems trivial (it isn’t really anymore). Trivial things lead to more complex things, and my impression is that HtDP does just this. I was wondering why these books have such weird names: • How to Design Programs • Structure and Interpretation of Computer Programs When I just thought these were just guides and references on Racket and Scheme (so why don’t they call them that?), but they seem to be much more, from what I have heard from others (haven’t read either of the above). Yeah… I am probably wasting too much time in my life doing meaningless things like surfing facebook or something, but this is probably a good way to spend the holidays. Thank you for your insight, it makes a lot of sense
Paredit-mode is a problem if you’re a literate-programming user. I use a workaround for that, but that’s all irrelevant to you if you don’t edit Lisp together with non-Lisp in the same document.
Like Bertrand Russell would probably say — every once in a while it’s a good idea to put a question mark on those things we take for granted. I think you just did that with how you spend your time. :slightly_smiling_face: You’re welcome. Thanks for the conversation! I hope you enjoyed as much as I did.
That reminds me, a few months ago I bought Bertrand Russell’s autobiography from the local library’s book sale, but haven’t gotten around to reading it, LOL. I’ve just stopped reading books. I need to start again. Yes, I really enjoyed the conversation :slightly_smiling_face:
Sorry, going through some older logs a bit, and I came across this example from sorawee of the dot notation.
So, this portion of the documentation sort of explains what’s happening: https://docs.racket-lang.org/reference/reader.html?q=dot#%28part._parse-pair%29
But I cannot understand these paragraphs, even though I have read over them several times: > If the reader finds two data between the matching parentheses that are separated by a delimited ., then it creates a pair. More generally, if it finds two or more data where the last datum is preceded by a delimited ., then it constructs nested pairs: the next-to-last element is paired with the last, then the third-to-last datum is paired with that pair, and so on. > > If the reader finds three or more data between the matching parentheses, and if a pair of delimited .s surrounds any other than the first and last elements, the result is a list containing the element surrounded by .s as the first element, followed by the others in the read order. This convention supports a kind of infix notation at the reader level. Especially things with 2 or more infix dots is confusing the heck out of me.. At the end, this is also stated: > If a delimited . appears in any other configuration, then the https://docs.racket-lang.org/reference/exns.html?q=dot#%28def._%28%28lib._racket%2Fprivate%2Fbase..rkt%29._exn~3afail~3aread%29%29\|exn:fail:read exception is raised. Similarly, if the reader encounters a ), ], or } that does not end a list being parsed, then the https://docs.racket-lang.org/reference/exns.html?q=dot#%28def._%28%28lib._racket%2Fprivate%2Fbase..rkt%29._exn~3afail~3aread%29%29\|exn:fail:read exception is raised. I guess this paragraph I don’t understand because I am unclear on what configurations of dot is allowed and not allowed.
For example, I don’t really see why this is allowed, or how it would be represented in the usual cons
or list
notation: '(foo a b . res)
Also, why is (a . b . c)
equivalent to '(b a c)
? Is there any particular reason this happens?
Not sure if the documentation prohibits more than two infix dots, but any more than 2 do raise errors it seems. Another question for me is what happens when there are multiple things between/before/after the dots (both in the case of using 2 dots, and also the case of using one single infix dot) Eg. '(foo bar . quux baar . baar ba b)
Eg. '(fooo asdfdasf . racket is great)
Sorry if this is a confusing question..
I’m pretty sure it’s in the docs (can’t look it up right now) but it is so you can do (num1 . < . num2)
or related infixy things
Hmm, I might have put too much stuff into a single question. A simpler and shorter question is why does '(1 . 2 . 3)
produce '(2 1 3)
? Any particular reason?
@samdphillips gave the answer
Another part of the reason is that quoted data is read the same way as code.
I’m not sure what you mean by quoted data is read the same way as code… hmm.. how would this motivate this type of infix notation?
Further, notation such as (num1 . < . num2 . < . num3)
doesn’t work, so I guess we are limited to just 2 dots then.
The motivation is so you can write (a . < . b)
instead of (< a b)
And yes, it’s limited to only two dots
Thanks!
The next question is how to represent '(foo a b c d . res)
is list or cons notation
Can this be done?
This seems to be valid Racket code
But not sure how to convert to cons
(cons 'foo (cons 'a (cons 'b (cons 'c (cons 'd 'res)))))
It’s not a list
So it is an improper list
I see
I was going to ask how to represent this in cons
: '(foo a b . c d)
but that seems to be invalid Racket code
Only one thing after the dot then, I see
I understand this perfectly now: > More generally, if it finds two or more data where the last datum is preceded by a delimited ., then it constructs nested pairs: the next-to-last element is paired with the last, then the third-to-last datum is paired with that pair, and so on.
@plragde was saying that dotted pairs are a historical artifact to save one word of memory? Is this true? Was this introduced in the original LISP 1 then? Not entirely sure why it would save one word of memory… hmmmm
I can’t find any documentation on it being used in previous LISPs
Doesn’t explain the saving of the word of memory, but demonstrates existence before Racket. huh
Wait, one thing that still doesn’t make sense, if we are passing racket_value*
, can’t we do *racket_value
and modify the underlying value in the function itself? Is this somehow prevented in C, or some error raised? Sending pointers into a function obviously means you can dereference and modify the underlying value.. This means: (define (foo a)
;; dereference a and modify the original value, since a is technically a pointer
...
)
(define x 42)
(foo x)
foo
will have changed x
’s value, right? Is this prevented somehow in the C code? @rokitna
I’m confused about about what you mean when you say “sharing stacks.” Isn’t there usually just one call stack anyway (unless threads or continuations or coroutines are in the mix)? The way I understand C-like architectures, when you call a function, you push a frame onto the stack to keep track of what address to jump back to. It’s still the same stack, just with another frame on it.
As for whether you can write { *v = compute_new_v(); }
when v
is a (racket_value *)
… the thing is, Racket doesn’t have any operation that does that, at least in typical code. Maybe it’s possible to do that from the C side or by using something in the unsafe modules. (As @soegaard2 says, some small and common immutable values such as fixnums are passed around directly rather than using pointers in practice, so even if there’s a way to do this for the ones that are pointers, it’s not going to work for those other ones.)
Neat, thanks! Might be a good idea to add this to standard library. At the time I asked, I wrote it by subtracting in a loop :smile:
Not sure what fixnums are (are these a construct in C? I looked up “fixnums in C” but I only got search results about things in Ruby, maybe this is the union you were referring to before?)
Yeah, sorry about the confusion with the stack, you are correct. I recently wrote a compiler for a basic subset of C (in Racket) so I understand what you mean, yeah. I should have said “sharing frames on the stack”, I meant “frame” instead of stack.
Ah, that makes sense, I was just misunderstanding, yeah. Although, if only the pointer to the variable ( racket_value*
) is passed, how does the called function in Racket know the racket_value
? Wouldn’t this involve dereferencing? Does Racket then somehow implement “read-only” dereferencing? Oh, maybe a const
pointer?
The reason (a b c . func . d e f)
is equivalent to (func a b c d e f)
is just to allow people to write certain notations like (a . -> . b)
and (a . < . b)
in arrangements that are a little more familiar, rather than having to write (-> a b)
and (< a b)
.
(foo a b . res)
is an “improper list” (a series of cons cells that ends with something other than null
). Every non-dotted list (a b c)
is shorthand for (a . (b . (c . ())))
where (a . b)
corresponds roughly to (cons a b)
and ()
corresponds to null
. To save on parens, multiple nested cons cells can be merged together like (a b c . ())
, and to make proper lists easier to write, that last . ()
can be omitted.
This dotted notation for cons cells has been around for as long as s-expressions have, I think. However, I think the two-dot notation for infix is something that’s novel to Racket.
Ah, you already got a bunch of these answers in the main thread. XD lol
Yeah, I made this question way too long for no particular reason, sorry you had to read that wall of text
Sometimes I hear people concerned with trying to allocate as little as possible, for performance (especially in historical environments with slow garbage collectors and limited memory). Some Lisp and Scheme programmers like to model their data structures in terms of cons cells and symbols (sometimes just like s-expressions, and sometimes more like binary trees), so the cons
operation is a frequent source of allocations when operating on that data. Building an improper list saves on one cons
call compared to a proper list.
No problem! :stuck_out_tongue:
In a sense, there isn’t even read-only dereferencing. In this allegory, all Racket expressions and function arguments are of type (racket_value *)
. If we add a dereferencing operation to the language, it would return something of type racket_value
, which would be useless since what everything wants is still a (racket_value *)
.
In that case, when do you ever actually interact with the data? In between. The basic operations take a pointer, dereference it, compute with that data, and return another pointer.
Lisp dialects, including Scheme and Racket, typically have arbitrary-precision numbers. As long as their binary encodings are small enough to fit in memory, you can generate and work with large numbers (like factorials). But it’s still the case that arithmetic on smaller numbers, which more directly corresponds to CPU instructions, is more likely to be fast. Fixnums (fixed-precision numbers) are the numbers small enough to have meaningfully optimized arithmetic.
Fixnums are contrasted to to bignums (arbitrary-precision numbers). You might also come across fixints and bigints, the integer-specific versions of these terms.
Thanks! > In that case, when do you ever actually interact with the data? In between. The basic operations take a pointer, dereference it, compute with that data, and return another pointer. So dereferencing does happen somewhere then? I am just wondering where this dereferencing happens. Say we call (foo a)
where the value of a
is 1. Now, in the function signature of foo, we have (foo x)
. So the original location (pointer) of a
is still stored in the stack frame of the caller, but the callee (foo
) must create a location (pointer) for x
, where it stores *a
. So does this dereferencing happen during before the function call, or after the function call? How is it possible that we totally hide the underlying racket_value
, is what I am kind of getting at (maybe?)
I’m not sure I follow. If you’re just passing an argument to a function, nothing needs to be dereferenced, since you have a pointer and the function is expecting a pointer.