
Is the behaviour you expect from eq? documented?

it’s the behavior from string-append
that’s confusing

Racket BC apparently has an invariant that any mutable empty string must be zero_length_char_string
.


As mentioned earlier, this is problematic as destructive operations like unsafe-string->immutable-string!
exists.

??? that is so bizarre

So there are two possible fixes

One is to get rid of this invariant

Another is to have a special case for unsafe-string->immutable-string!
to recognize zero_length_char_string
and not destructively update it.

the second sounds less likely to cause bugs elsewhere

What does a zero-length mutable string mean? :thinking_face:

means someone forgot to use string->immutable-string
before returning something to callers

I mean, there is just nothing to mutate in a zero-length string…:zany_face:

except, ironically, the fact that it’s mutable

Maybe a zero-length string should imply that it’s immutable.

So, if a string is an empty string, it’s automatically an immutable string. And string-append
returns a mutable string iff the string is non-empty.

IMHO, empty string should be a constant across the entire system.

instead of adding special cases to string-append
(or mutable strings in general), why not accept this as a consequence of using an unsafe operation? the behavior makes sense the way it is, and I would be surprised if this were the only way to use unsafe operations to mutate the behavior of the core libraries
[edit: added a missing “and”]

Welp, checked the string-append
documentation: “Returns a new mutable string[…]” There does seem to be a bug or clarification to be considered there.

yeah even for things that we can’t make return immutable strings, I’d say there’s a strong user expectation that they not share mutable strings returned across multiple calls

> If no _str_s are provided, the result is a zero-length string. Interestingly, the docs did not say whether the zero-length string is mutable. :zany_face:

We can’t fix it at the level of string-append
, cause other operations have the same problem

E.g.,
(define s (string #\A #\p #\p #\l #\e))
(define t (substring s 0 0))
(define u (substring s 1 1))
(immutable? t) ; #f
(immutable? u) ; #f
(eq? t u) ; #t

In fact, lol
(define s (list->string '()))
(define t (list->string '()))
(eq? s t) ; #t
> list->string > Returns a new mutable string

Well, that makes it pretty clear that the intended behaviour is to be able to return mutable empty strings

strings. plural. as in multiple mutable strings :p

It’s just how we perceive things. It wouldn’t be a problem if empty string is inherently immutable.

then perhaps it wouldn’t be a problem if we changed things so the empty string is always immutable, and there simply was no such thing as a mutable empty string…

that might be a 2am idea rather than a good idea

Why would a mutable empty string be a bad idea? (as long as these functions return a new mutable empty string each time)

Maybe just like empty list. (eq? empty empty) ; #t
(eq? empty null) ; #t
(eq? '() '()) ; #t
(eq? '() null) ; #t
No matter how many empty lists there are, they will not take up any new space. Could it be a benefit that empty tests might be more efficient?

these lists are immutable, but the docs for substring
(for example) say that the result is mutable. It would be non-uniform to return an immutable empty string or a mutable non-empty string.

(and non-uniformity = more things to remember. The user memorises only the general case, then expects uniformity, leading to confusion and annoying-to-track bugs)

For Rhombus, we could make immutable strings the default though.

Well, I see mutability as a property. Maybe a mutable empty string is okay. But there is nothing to mutate, actually. :thinking_face:

unless suddenly someone adds a string-append!
function. Then you should have: (define s (string))
(eq? s (string-append! s "abc")) ; #t is expected
(edit: (string)
was ""
)

Well, ""
is immutable, and string-append!
can’t exist IIUC because string allocates memory as many as it needs, so it can’t extend.

Er, yes for "". The other is an implementation issue (which i thought of), I was at the api level

(And it would be possible if mutable strings were actually immutable strings wrapped in a struct)

I think there should be two zero-length string constants, but that might be inconveneient for the runtime curretly

According to the API document. The length of a string is immutable. > A _string_ is a fixed-length array of characters. https://docs.racket-lang.org/reference/strings.html

@samth you mean there should be exactly one for immutable and exactly one for mutable? (this is already the case for Racket BC IIUC)

Yes. If thats the case already it should be easy to fix

Interestingly, (eq? (make-string 0) (make-string 0))
is #f
.

is +nan.0 the only number such (not (= x x))
?

+nan.f

I can’t think of others.

Is (equal? +nan.f +nan.0)
?

They’re eq?

thanks!

Hmm. I wonder whether they are eq? on all platforms?

> +nan.0 (not-a-number), and +nan.f (single-precision variant, when enabled)


so depends on compiler flag, I think

not necessarily eq?

From Sorawees link: Although we write +inf.f, -inf.f, and +nan.f to mean single-flonums, those forms read as double-precision flonums by default, since read-single-flonum is #f by default.

Oh, there’s also any complex number with a nan component (but I was actually only interested in real?
numbers).

> (eq? +nan.0 +nan.0)
#t
> (/ +inf.0 +inf.0)
+nan.0
> (eq? (/ +inf.0 +inf.0) +nan.0)
#f

So the eq?-ness in the first example is accidental.

I think that’s because +nan.0
that is apparent in source code is interned.

However, they are equal?
in the last example.

Makes sense that the reader always produces the same value.

It could be because there are many different forms of NaN and those two expressions produced different kinds.

Best to use nan?
anyway

definitely

Here’s the definition of nan?
: (define (nan? x)
(unless (real? x) (raise-argument-error 'nan? "real?" x))
(or (eqv? x +nan.0) (and (single-flonum-available?)
(eqv? x (real->single-flonum +nan.0)))))

Logically based on eqv? rather then eq?

So, I grok that +nan.0
is represented via an IEEE float and so technically a “real”, but it literally means “not a number”, which should imply (real? +nan.0) == #f
. :wink:

nan is basicly an “invalid” value that calucaltions can return. real?
just checks if a value has a datatype of a number, not that it actually is a number

Welcome to Racket v7.9 [cs].
> (eq? (make-string 0) (make-string 0))
#t

#f
is from BC

Because it always allocate a new object: https://github.com/racket/racket/blob/master/racket/src/bc/src/strops.inc#L77

I know. Just noting the irony in the naming.

(not (number? +nan.0))

Can the contract system recognize if a procedure doesn’t return (i.e. replaces its continuation)… if not with normal contracts, perhaps with temporal contracts?

(I asked similar questions in the past, but I wonder if this framing will be more useful.)

This would be a pretty unusual contract. I don’t know of anything existing that does that, but it would be fun to try to write something like this.

Maybe something like this? https://gist.github.com/camoy/9643b6e73e6e6fbeeb801245e1599d05

That looks like it could work.

hmm, doesn’t putting a contract on a function usually stop it from doing tail calls anyway, since the modified function has to project the output through a contract before returning it?

I don’t know if the contract system has some tricky way to make tail calls keep working. I imagine it may be possible to have an advanced enough system of collapsible contracts so that when e.g. a function tail-calls itself, it doesn’t create a stack frame to do the output projection because the outer call already put a stack frame in place to do that

In the context that I’m considering (purity enforcement) tail-calls aside, I suspect that using dynamic-wind
pervasively would have a heavy impact on performance.

Like, I imagine that system could be pursued, but I have no idea if Racket has a system like that already in place. (And if it did, that’s a somewhat different matter than whether contracts can enforce tail call behavior.)

Using contracts does have a heavy impact on performance, especially detailed ones.
(Incidentally, for performance reasons, I’ve been thinking about writing a system of typed effectful abstractions for Racket that are all inlined at macroexpansion time, so that there isn’t run-time error checking, closure allocation, control-passing, etc. to do every time an effect is used. I think this system would involve a steep departure from Racket idioms, though.)

Ohhh, I totally misread your original question as being about tail calls, rather than about functions that unwind their computation in other ways. :sweat_smile: In that light, camoy’s code example finally makes sense to me.