@mskoh52 has joined the channel
Quick Q on the release notes: when it says that CS supports all of the features of BC, that doesn’t mean single-floats were implemented, right?
(Not a problem, it’s not something I really need, but it’s something I’m tracking for Herbie reasons.)
I think the answer is no - but here is how to check: https://docs.racket-lang.org/reference/number-types.html#%28def._%28%28quote._~23~25kernel%29._single-flonum-available~3f%29%29
You’re right. In retrospect, “all features” really means “all features that we expect to ever implement”, where single-precision and extended-precision floating-point are the two things not on that list.
@pavpanchekha Racket CS could easily provide an operation that would round a flonum to reflect the value that would fit in a single-precision floating-point number. Would that be useful?
Eh, I wouldn’t bother. The long answer is that Herbie is in the process of making number types “pluggable”. Right now we ship single precision by default but in the future I expect not to (or maybe only enable it by default in BC or something).
To make a plugin, you provide a way to do each fundamental operation to that number type (along with some other things). We already link to libm
for everything except basic arithmetic (those aren’t functions so we can’t FFI-bind them directly)
It is not going to be hard to write a little C wrapper for single-precision add, multiple, divide, and subtract!
In principle a double→single function would mean we can implement that without C, by treating singles as a subset of doubles, but why bother?
I’d rather this weird feature live in a Herbie thing than in the Racket platform.
(And of course a shout-out to Brett Saiki and David Thien for doing all the work splitting number types in Herbie.)
Ok!
FWIW, you can already get double->single rounding by using ffi/unsafe
to write a _float
and read it back, or call a C _float
identity function.
A built-on operation would just be 10–100x faster by keeping the conversion in a register. If performance is a concern for the special case of single-precision floats, a pure Racket function that uses fl
operations plus a rounding operation would also be faster than calling a C function for arithmetic.
Performance is not much of a concern. Though it surprises many, computing with floating-point numbers is a small percentage of what Herbie does, about 10% by runtime.
(For contrast, computing with bigfloats is 50%)
So even less reason to add this function :slightly_smiling_face:
I’m starting to think I’ll add it, anyway, because it seems useful for some other contexts.
@samth should Intersection types be appy-able? I expected this program to work, but it says the intersection is not a function type. #lang typed/racket/base
(require/typed racket/base
(values (Intersection (-> String String) (-> Symbol Symbol))))
(values 'A)
Putting an ann
around values fixes that problem. But then, the Intersection can’t be turned into a contract.
If there’s no way to get a type like this, then I won’t open an issue
There’s not much need for that since you can use case->
But probably it should work
Well, heh, I won’t stop you!
I got to that program by playing with occurrence type props. … So I think we’re okay now, but will want to fix if there’s a good reason for occurrence typing to add a function type.
Or maybe, occurrence typing should extend/make a case->
instead of an Intersection
@samth is “Racket BC” “before Chez?” or does it stand for something else?
@gknauth yes
thanks. cute.
Before Chez, or bytecode
The double meaning is part of the attraction
i’m learning racket…is it ok if i post some code for feedback?
Yes, you’re welcome to post code in either channel
Can anyone help me understanding
> In case extracting elements from @racket[s] involves a side effect, > they will not be extracted until the first element is extracted from > the resulting stream. in https://docs.racket-lang.org/reference/streams.html?q=stream-tail#%28def._%28%28lib._racket%2Fstream..rkt%29._stream-tail%29%29?
@sorawee I think it means that if you have a stream like (stream (explode) 1 2 3)
and call stream-tail
on it, (explode)
won’t be evaluated yet. However once you do something like (stream-first (stream-tail s 1))
then (explode)
will be evaluated even though it’s skipped over by stream-tail
.
(define xs (stream (println 'a) 2 3))
(stream-first xs)
(stream-tail xs 1)
a
appears only once.
yup that’s correct, because each element of a stream is only ever forced at most once
which makes sense, but then we are back at the original question: what does that paragraph mean?
I think it’s saying that in this code, 'a
also appears once
(stream-first (stream-tail xs 1))
But in this code, it doesn’t appear at all
(define xs (stream (println 'a) 2 3))
(stream-tail xs 1)
But in the first code, 'a
doesn’t appear.
I would be very surprised if it does. stream-tail
is really a bunch of stream-rest
, so it has no business with the first-expr position of stream-cons
.
huh, it doesn’t? (haven’t tried)
it doesn’t (I’m not sure if I should reply with yep or nope here)
neat. I guess it’s just saying that stream-tail
doesn’t force any elements at all
It’s worded pretty awkwardly
The conclusion I’m drawing is that the doc is needed to be improved lol
I wonder if it forces the next-stream expressions
which is what I’m doing right now actually
There’s actually another doc bug in stream-cons
It says that:
> Produces a lazy stream for which stream-first forces the evaluation of first-expr to produce the first element of the stream, and stream-rest forces the evaluation of rest-expr to produce a stream for the rest of the returned stream.
like if a stream is made with (stream-cons 1 (print-stuff-and-make-rest-stream))
will stuff be printed when stream-tail
is called on it?
Yep, the answer is no :slightly_smiling_face:
(stream-rest (stream-cons 1 (error 'unreached)))
even though the doc says “stream-rest forces the evaluation of rest-expr to produce a stream for the rest of the returned stream.”
and I assume if you skip over multiple rest-exprs with stream-tail
, they’re still evaluated when stream-first
is used on the tail stream, even though their corresponding element expressions aren’t evaluated
Can you reword your reply? I’m not sure if I understand
like
one sec
moving to laptop
so I have this program: #lang debug racket
(define s
(stream-cons #R(values 1)
#R(stream-cons #R(values 2)
#R(stream-cons #R(values 3)
#R(stream 4 5 6)))))
(stream-tail s 2)
it prints out this when run: (stream-cons
(report (values 2))
(report (stream-cons (report (values 3)) (report (stream 4 5 6))))) = #<stream>
#<stream>
so even though it’s skipping the first two elements, only the first rest-expr is evaluated. the second rest-expr isn’t evaluated yet.
(first rest-expr = stream starting with 2, second rest-expr = stream starting with 3)
however, if I only skip the first element using (stream-tail s 1)
, none of the rest-exprs are evaluated at all, which makes me wonder what would happen if I did (stream-tail (stream-tail s 1) 1)
instead of (stream-tail s 2)
fascinating. (stream-tail (stream-tail s 1) 1)
forces the first rest-expr but not the second, just like (stream-tail s 2)
yeesh, laziness is complicated
Yeah, all of these are due to the doc bug of stream-cons
I mentioned above
In my local racket, I have this:
@defform[(stream-cons first-expr rest-expr)]{
Produces a lazy stream for which @racket[stream-first] forces the
evaluation of @racket[first-expr] to produce the first element of
the stream, and @racket[stream-rest] produces a lazy stream
for which stream operations (@racket[stream-empty?], @racket[stream-first], @racket[stream-rest])
forces the evaluation of @racket[rest-expr] to produce the rest of the returned stream.
The first element of the stream as produced by @racket[first-expr]
must be a single value. The @racket[rest-expr] must produce a stream
when it is evaluated, otherwise the @exnraise[exn:fail:contract?].
After the evaluation of @racket[first-expr] or @racket[rest-expr],
the result is memoized so that subsequent access does not recompute
the expression.
@examples[#:eval sequence-evaluator <elided>]
}
It’s really, really weird that (stream-rest (stream-cons first-expr rest-expr))
doesn’t force rest-expr
but (stream-rest (stream-rest (stream-cons first-expr rest-expr)))
does.
or at least, weird to me. I don’t understand the use case for that.
The second one must do, right? It definitely needs to evaluate rest-expr
to see what’s going on inside it.
oh I think I get why - because stream-rest
has to throw if the stream is empty
The first one is weird, I agree
okay yes (stream-rest (stream))
throws immediately, so I see where the forcing happens
it has to determine that the stream is not empty. but in the case of a stream created with stream-cons
, you can check that it’s not empty without forcing either the first or rest expressions
Correct
so if the goal is to be maximally lazy, then the behavior of stream-rest
is good, because it doesn’t need to force the rest - it just needs to prove that a rest exists
Not really
Consider:
(stream? (stream-rest (stream-cons 1 (error 'bad))))
(stream-empty? (stream-rest (stream-cons 1 (error 'bad))))
first one passes and second one throws?
The first one returns #t
. The second one errors
Yep
yeah that seems correct to me
it does create a weird thing though
empty streams can throw!
I mean, it fits my mental model of what the stream library is doing, but I am not sure if I would describe it as “correct”
yeah, same
I’ll take a transducer pipeline or for loop any day over this, I can actually tell when evaluation starts and ends with those
oh, an added wrinkle. this code is not as lazy as I would expect: #lang debug racket
(stream* #R(stream 1 2 3))
it prints out this: (stream 1 2 3) = #<stream>
#<stream>
even though I would have assumed that (stream* s)
was equivalent to (stream-rest (stream-cons 'ignored s))
Yep, I found that, too
There are a lot of weird things in racket/stream
Here’s another one: stream-take
will screw up a multivalued stream, but stream-tail
and stream-append
will not.
ugh
I think “avoid streams” might be reasonable general advice at this point
Writing examples for these functions is very difficult. There are so many things to concern about
Multiple values, when things are forced, memoization.
yeah and it’s really hard to tell what behavior is intentional
@sorawee maybe bundle a few issues?
Several multiple values issues can be fixed, I think. PR submitted for stream-take
, and I think I can handle stream-map
, stream-filter
, and stream-add-between
, too.