
@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.