laurent.orseau
2020-12-15 08:24:43

Is the behaviour you expect from eq? documented?


notjack
2020-12-15 08:26:49

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


sorawee
2020-12-15 08:29:25

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



sorawee
2020-12-15 08:30:50

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


notjack
2020-12-15 08:30:51

??? that is so bizarre


sorawee
2020-12-15 08:31:42

So there are two possible fixes


sorawee
2020-12-15 08:31:47

One is to get rid of this invariant


sorawee
2020-12-15 08:32:18

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


notjack
2020-12-15 08:34:49

the second sounds less likely to cause bugs elsewhere


wanpeebaw
2020-12-15 09:21:47

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


notjack
2020-12-15 09:26:19

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


wanpeebaw
2020-12-15 09:30:14

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


notjack
2020-12-15 09:30:39

except, ironically, the fact that it’s mutable


wanpeebaw
2020-12-15 09:32:18

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


wanpeebaw
2020-12-15 09:42:20

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.


wanpeebaw
2020-12-15 09:51:38

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


rokitna
2020-12-15 09:56:14

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”]


rokitna
2020-12-15 10:01:25

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


notjack
2020-12-15 10:02:06

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


wanpeebaw
2020-12-15 10:05:23

> 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:


sorawee
2020-12-15 10:10:42

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


sorawee
2020-12-15 10:11:28

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


sorawee
2020-12-15 10:13:36

In fact, lol

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


laurent.orseau
2020-12-15 10:14:44

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


notjack
2020-12-15 10:17:01

strings. plural. as in multiple mutable strings :p


wanpeebaw
2020-12-15 10:18:17

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


notjack
2020-12-15 10:20:00

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…


notjack
2020-12-15 10:20:35

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


laurent.orseau
2020-12-15 10:25:35

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


wanpeebaw
2020-12-15 10:39:51

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?


laurent.orseau
2020-12-15 10:47:58

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.


laurent.orseau
2020-12-15 11:03:25

(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)


laurent.orseau
2020-12-15 11:04:47

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


wanpeebaw
2020-12-15 11:06:52

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


laurent.orseau
2020-12-15 11:11:35

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 "")


sorawee
2020-12-15 11:32:00

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


laurent.orseau
2020-12-15 12:11:29

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


laurent.orseau
2020-12-15 12:26:37

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


samth
2020-12-15 14:12:03

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


wanpeebaw
2020-12-15 14:12:15

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


sorawee
2020-12-15 14:18:04

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


samth
2020-12-15 14:20:05

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


ryanc
2020-12-15 14:20:44

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


dvanhorn
2020-12-15 23:05:06

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


soegaard2
2020-12-15 23:08:43

+nan.f


soegaard2
2020-12-15 23:09:14

I can’t think of others.


dvanhorn
2020-12-15 23:09:42

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


jaz
2020-12-15 23:10:10

They’re eq?


dvanhorn
2020-12-15 23:10:15

thanks!


soegaard2
2020-12-15 23:11:11

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


sorawee
2020-12-15 23:11:47

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



sorawee
2020-12-15 23:12:04

so depends on compiler flag, I think


jaz
2020-12-15 23:12:17

not necessarily eq?


soegaard2
2020-12-15 23:12:36

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.


dvanhorn
2020-12-15 23:13:08

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


jaz
2020-12-15 23:13:47

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


jaz
2020-12-15 23:14:37

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


sorawee
2020-12-15 23:14:40

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


jaz
2020-12-15 23:14:43

However, they are equal? in the last example.


soegaard2
2020-12-15 23:15:38

Makes sense that the reader always produces the same value.


me1890
2020-12-15 23:49:31

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


laurent.orseau
2020-12-16 01:16:07

Best to use nan? anyway


me1890
2020-12-16 01:25:05

definitely


jaz
2020-12-16 01:32:06

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)))))


laurent.orseau
2020-12-16 01:39:01

Logically based on eqv? rather then eq?


massung
2020-12-16 02:15:47

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:


me1890
2020-12-16 03:24:41

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


wanpeebaw
2020-12-16 03:59:08

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


sorawee
2020-12-16 04:00:08

#f is from BC


sorawee
2020-12-16 04:01:45

massung
2020-12-16 04:29:20

I know. Just noting the irony in the naming.


notjack
2020-12-16 04:32:03

(not (number? +nan.0))


kellysmith12.21
2020-12-16 05:08:59

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?


kellysmith12.21
2020-12-16 05:13:25

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


camoy
2020-12-16 05:29:26

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.


camoy
2020-12-16 05:48:55

kellysmith12.21
2020-12-16 07:15:56

That looks like it could work.


rokitna
2020-12-16 07:16:00

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?


rokitna
2020-12-16 07:19:07

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


kellysmith12.21
2020-12-16 07:21:55

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.


rokitna
2020-12-16 07:24:35

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


rokitna
2020-12-16 07:35:41

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


rokitna
2020-12-16 07:38:20

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.