29
78
Fork
You've already forked r7rs
2

New lexical syntaxes and immutability #80

Open
opened 2022年05月08日 15:12:24 +02:00 by johnwcowan · 5 comments

Based on CL practice, people have found it useful to have lexical syntax for arrays, bitvectors, and transparent record types in addition to the ones in Scheme. But I realized that every time we introduce a novel lexical syntax for the various container types of the large language (I don't mean a variant like #vu8 vs #u8), we have to deal with the existence of mutable and immutable variants of the underlying data types. I had been hoping to put off the question of lexical syntax, as it will require a unified design, but for every such lexically representable type, there will need to be some way for the standard library functions of that type to return mutable or immutable values.

  1. We can say that read and eval are the only way to generate immutable values, but this makes it very annoying to use them when immutability is an important property (e.g. when sharing data between threads).

  2. We can have a function or set of functions that creates an immutable copy of a mutable container, but this not only involves extra data motion, it also makes it hard or impossible for immutable containers to share their contents freely (this is especially important when dealing with immutable strings).

  3. We can say that R7RS-large implementations are required to copy lexically apparent containers in code, so that there is no absolute need for immutable objects, although they are still useful for sharing.

I don't like any of these.

Based on CL practice, people have found it useful to have lexical syntax for arrays, bitvectors, and transparent record types in addition to the ones in Scheme. But I realized that every time we introduce a novel lexical syntax for the various container types of the large language (I don't mean a variant like `#vu8` vs `#u8`), we have to deal with the existence of mutable and immutable variants of the underlying data types. I had been hoping to put off the question of lexical syntax, as it will require a unified design, but for every such lexically representable type, there will need to be some way for the standard library functions of that type to return mutable or immutable values. 1. We can say that `read` and `eval` are the only way to generate immutable values, but this makes it very annoying to use them when immutability is an important property (e.g. when sharing data between threads). 2. We can have a function or set of functions that creates an immutable copy of a mutable container, but this not only involves extra data motion, it also makes it hard or impossible for immutable containers to share their contents freely (this is especially important when dealing with immutable strings). 3. We can say that R7RS-large implementations are required to copy lexically apparent containers in code, so that there is no absolute need for immutable objects, although they are still useful for sharing. I don't like any of these.
Member
Copy link
  1. This solution would also force read and eval to be primitives, which would be bad. They should be implementable in Scheme if possible. (For eval, this is currently only approximately true because there are no other primitives dealing with environments.)

[...] it also makes it hard or impossible for immutable containers to share their contents freely (this is especially important when dealing with immutable strings).

What do you mean by this?

1. This solution would also force `read` and `eval` to be primitives, which would be bad. They should be implementable in Scheme if possible. (For `eval`, this is currently only approximately true because there are no other primitives dealing with environments.) > [...] it also makes it hard or impossible for immutable containers to share their contents freely (this is especially important when dealing with immutable strings). What do you mean by this?
Author
Owner
Copy link

Suppose we have SRFI 153 or some similar library, where all procedures that return strings return mutable strings, plus a way of making immutable copies of mutable strings. Mutable strings cannot share storage, but immutable strings can: a substring S' of an immutable string S can share storage with S. But if substring returns a mutable string S'', we have lost the connection to S, and so an immutable copy S''' of S'' cannot share storage with S. In code:

(define s "abcdefghi") ; s is immutable
(define s1 (substring/immutable s 3 6)) ; s1 and s can share storage
(define s2 (substring s 3 6)) ; s2 cannot share storage with s
(define s3 (immutable-copy s2)) ; s3 cannot share storage with s
Suppose we have SRFI 153 or some similar library, where all procedures that return strings return mutable strings, plus a way of making immutable copies of mutable strings. Mutable strings cannot share storage, but immutable strings can: a substring S' of an immutable string S can share storage with S. But if `substring` returns a mutable string S'', we have lost the connection to S, and so an immutable copy S''' of S'' cannot share storage with S. In code: ``` (define s "abcdefghi") ; s is immutable (define s1 (substring/immutable s 3 6)) ; s1 and s can share storage (define s2 (substring s 3 6)) ; s2 cannot share storage with s (define s3 (immutable-copy s2)) ; s3 cannot share storage with s ```
Member
Copy link

The use case of sharing data between threads securely has a solution that does not rely on the form of immutability we have discussed here.

Namely, instead of sharing (raw) data, only objects (in the wider sense of object-oriented programming) could be exchanged. Say, instead of providing a different thread access to a vector v, just a procedure of the form

(lambda (i)
 (vector-ref v i))

is made accessible.

This does not solve all problems for which immutable data structures are a solution (e.g. effective sharing of substructures), but it handles one major use case so we can possibly concentrate on the other needs in the further discussion.

The use case of sharing data between threads securely has a solution that does not rely on the form of immutability we have discussed here. Namely, instead of sharing (raw) data, only objects (in the wider sense of object-oriented programming) could be exchanged. Say, instead of providing a different thread access to a vector `v`, just a procedure of the form ```scheme (lambda (i) (vector-ref v i)) ``` is made accessible. This does not solve all problems for which immutable data structures are a solution (e.g. effective sharing of substructures), but it handles one major use case so we can possibly concentrate on the other needs in the further discussion.
Author
Owner
Copy link

What about vector-length? That would have to be passed along too. And then all the non-primitive vector procedures would have to be rewritten. And either they would have to be monomorphic (two versions of vector-append, one for mutable and one for immutable vectors) or ad hoc polymorphic, in which case the existing problems would remain unsolved, including the relationship between these OO immutable vectors and vector literals.

I think this is a complete non-solution.

What about `vector-length`? That would have to be passed along too. And then all the non-primitive vector procedures would have to be rewritten. And either they would have to be monomorphic (two versions of `vector-append`, one for mutable and one for immutable vectors) or ad hoc polymorphic, in which case the existing problems would remain unsolved, including the relationship between these OO immutable vectors and vector literals. I think this is a complete non-solution.
Member
Copy link

That would indeed be a complete non-solution, but this is not what I have in mind. Instead of providing all vector-related procedures just send what would be called in other languages a proxy object. The proxy object would be an implementation of the abstract interface necessary for communication and that would usually not include all vector-related procedures, say. As Scheme has no particular object data type, such a proxy object can be anything. In my simplified example above, it was just an accessor procedure. It could also be a record value or a procedure accepting and dispatching messages.

That would indeed be a complete non-solution, but this is not what I have in mind. Instead of providing all vector-related procedures just send what would be called in other languages a proxy object. The proxy object would be an implementation of the abstract interface necessary for communication and that would usually not include all vector-related procedures, say. As Scheme has no particular object data type, such a proxy object can be anything. In my simplified example above, it was just an accessor procedure. It could also be a record value or a procedure accepting and dispatching messages.
Sign in to join this conversation.
No Branch/Tag specified
main
pages
proc-draft
c-trichotomy
global-id-prop-ref
allow-van-tonder-like-expanders
No results found.
Labels
Clear labels
Batteries
Issues for the Batteries volume of the report, providing a standard library
Cleanup
Issues where inconsistencies or other infelicities have arisen
Consent Docket
Items which will be automatically be given a certain resolution if no objection is made by a certain date
Environments
Issues for the Environments volume of the report, providing OS interfaces and similar
Foundations
Issues for the Foundations volume of the report, related to the core language semantics
Macrological Fascicle
Meta
Procedural and organisational issues
Multiple proposals exist
Multiple competing, concrete proposals to resolve this issue exist (whether they are SRFI or still only pre-SRFI)
Pie-in-the-sky ideas
Ideas currently lacking any form of worked-out proposal
Problems without solutions
Problems we *must* solve but haven’t worked out any solution for yet
Procedural Fascicle
Public Comment
Publications
Issues related to the practicalities of making the report available
Question
For information and clarification
R6RS Compatibility
Issue touches on questions of R6RS compatibility
R7RS-small
Issues with the Small language Report
Resolution in draft
A resolution (not necessarily final) is specified in the current draft of the relevant fascicle
Specification exists
Proposals for which a viable, but incomplete specification text exists
SRFI/RnRS spec exists
A SRFI for this proposal exists, or a solution from another RnRS
Milestone
Clear milestone
No items
No milestone
Projects
Clear projects
No items
No project
Assignees
Clear assignees
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
scheme/r7rs#80
Reference in a new issue
scheme/r7rs
No description provided.
Delete branch "%!s()"

Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?