Am Fr., 5. Juni 2020 um 00:51 Uhr schrieb John Cowan <xxxxxx@ccil.org>: >> (2) It may make sense to add a linear update procedure `either-swap!'. > > > I don't see the point, given that Lefts and Rights are typically implemented as small fixed-size objects. Either-swap! would just have to change a tag in the internal record structure. This is preferable because no heap allocation would occur altogether. >> (3) Are there any guarantees stated in the actual specification that >> the "types" defined in this SRFI are disjoint to other Scheme types. > > > I've added "disjoint from each other and all other types", which is a dangling reference we won't be able to fix until late in the process when we have a comprehensive list of SRFIs and types. I'm not concerned about this sort of language until then. This will be a dangling reference forever because Scheme's model is not a closed-world model, won't it? Future SRFIs can always bring in new types. This is not specific to SRFI 189, but we should really think of something that makes sense logically. The approach I used in SRFI 146 was to reduce the question to the behavior of `define-record-type'. So what is really going on is that Scheme "types" are dynamic. They are values created by `define-record-type' (apart from the few predefined values), but these values are not first-class values. So the question, whether types are disjoint boils down to the question of whether locations are disjoint to each "type value" conceptually occupies a location in the store. This location is allocated through the evaluation of `define-record-type'. >> (4) Do we really want to restrict `equal' to an equivalence predicate >> in procedures like `maybe=' or `either='? A predicate like ´<' would >> also have its use cases. For that, the order of the arguments has to >> be specified, of course. > > > Fixed. Thanks. Now that I see it, I see that the restriction to two arguments isn't necessary either. (Numeric `=' and `<' allow for more than one argument either.) It is enough that the supplied `equal' relation takes as many arguments as there are following in the call to `maybe=' or `either='. >> (5) As discussed in another thread with Shiro, the default argument to >> `failure' in `maybe-ref' can be made implementation-specific and >> SHOULD default to a procedure that raises an error. > > > No, I want this to be an error that can be specifically detected. Hmmm... But this would make R7RS-large a bit irregular. Previously, as in SRFI 125, we just said "it is an error". And all reasonable implementations (outside an optimized unsafe mode) signal some error to the user. Personally, I do not see much value in `maybe-ref-error?' because a programmer can simply supply an appropriate failure procedure if the error has to be caught by the program. Moreover, the spec currently allows that `maybe-ref-error?' returns #t on all Scheme objects so I cannot `guard' for such errors. So, in the end, we are still no better off than in the SRFI 125 situation (which is not bad). However, "unsafe" optimizations are precluded with the current specification in SRFI 189, which is bad. >> (10) `maybe-sequence' and `either-sequence' take a `traversable' >> argument, but this kind of object seems to be not specified. Why do we >> restrict `map' to just one argument, > > > Because map functions that take multiple arguments are designed to process multiple traversables. It would be confusing if the standard map, vector-map, etc. procedures were not acceptable as values of the map argument. Thanks. That makes a lot of sense. But there is still no definition of `traversable' in the latest draft if I am not mistaken. >> (13) Please, please, reconsider the specification of `maybe->list' and >> `either->list'. It would be much more regular and helpful if these >> procedures returned #f in case of a Nothing (instead of the empty >> list) and, by regularity, in case of a Left as well. In Scheme, nil is >> a different value than false. Let's make use of this advantage! > > > I think it is more important that ->list functions always return a list, even though the distinction between Just no values and Nothing (and likewise for Either) is lost. Conversion functions are often lossy. Can you explain why it is important that they always return a list? Can you give some examples? On the other hand, the protocol that returns #f in case of Nothing seems to be much more helpful in the Scheme world: (cond ((maybe->list maybe) => (lambda (the-payload-as-a-list) ...)) (else ...)) In any case, while conversion functions can be lossy and sometimes we even want them to be lossy, I think that this conversion function shouldn't be. I would suggest the following (the same goes for the `either' procedures): (A) Keep `maybe->list' as is but restrict it to Maybes who Just payloads only consist of one value (and leave any extension up to the implementor). Add another `maybe->list/false' that works for arbitrary Maybes but return #f in case of a Nothing. (B) Change to semantics of `maybe->list' to the semantics of `maybe->list/false' above. The current conversion function is not only lossy, it loses the essentials (namely the distinction between Just and Nothing). The inverse of `maybe->list/false' (whatever it will be called) should also be included. >> (14) The problem with the maybe->values/value->maybe and >> either->values/values->either procedures is that they do not really >> set up a bijection. > > > Another lossy protocol, yes. If you want to guarantee no lossage, stick with the original Maybe or Either. Again the essentials are lost. Restrict the latest versions to Maybes and Eithers with payloads consisting of just one object. Code that would ever use the current extended versions would immediately smell. >> (15) For consistency and regularity, `values->either' should accept an >> arbitrary number of objs (including zero). The same is true for >> `lisp-values->either'. > > > The idea here is that you have a procedure that returns multiple values and you want to wrap it so it returns a container instead. If the multipel had to be passed as arguments, it would be necessary to reify them as a list and then to apply the appropriate constructor. By taking the procedure directly as an argument, this code can be provided once and for all inside values->maybe/either. I have not been talking about the values that would end up wrapped into a Right (should really be exactly one value by the reasoning above!!!), but about the values that would end up in the Left in case `producer' returns no values. In all other procedures in this SRFI, we are (now) allowing more than one `obj' argument. We should do here as well for consistency and regularity. >> (17) `lisp-values->maybe' and `lisp-values->either' have the same >> problem as maybe->values/either->values in that zero values are >> conflated/confused with Just/Right payloads consisting of zero values, > > > I've gotten rid of lisp-values<->either. So since maybe->lisp-values takes a Maybe and return exactly 2 values, the producer argument likewise produces two values and returns a Maybe. Thanks for restricting the allowed payload to consist of only a single object here! (Please do the same above as well. :)) >> (18) That the `successor' argument in `maybe-unfold' and >> `either-unfold' is not being used smells a bit [can one say so in an >> analog of "code smell"?]. The two procedures would be regular with >> respect to the various unfold procedures in R7RS-large if, after the >> seeds are stored as a payload, the successor is called on the seeds >> and if the stop procedure then doesn't yield #f on the resulting >> values. > > > Here's the recursion of unfold from SRFI 1, with the arguments renamed: > > (unfold stop? mapper successor seed &optional tail-gen) = > (if (stop? seed) (tail-gen seed) > (cons (mapper seed) > (unfold stop? mapper successor (successor seed)))) > > As you can see, stop? is the first procedure to be called. This is not what I meant. What I meant is that a consistent maybe version of `unfold' would be: (define (maybe-unfold stop? mapper successor . seed*) (if (appy stop? seed*) (nothing) (let* ((res (call-with-values (lambda () (apply mapper seed*)) just))) (assume (call-with-values (lambda () (apply successor seed*)) stop?) res))) >> (19) I still think that it is an error that the error MUST be >> signaled. I still propose to change the wording so that an error >> SHOULD be signaled ("encouraged" in the RNRS terminology) as this >> would not preclude Schemes offering a fast unsafe execution mode. > > > The point is that the default version of maybe-ref should always raise a non-resumable exception whenever there is a Nothing; that way, one may safely unpack Maybes with a guarantee that the procedure which does so will terminate abnormally. As long as one cannot guard against the signaled error (see my argument above) it's not helpful but just disallows certain optimizations. Do you know any Scheme that does not offer a safe mode where these errors aren't detected by the runtime system? >> (20) What about a `maybe-case' as well? In `maybe-if', the payload of >> the Just is thrown away, which is not what we always want. >> >> (define-syntax maybe-case >> (syntax-rules (just nothing) >> ((maybe-case e ((just . arg*) . body1) ((nothing) . body2)) >> (maybe-ref e (lambda () . body2) (lambda arg* body1))))) >> >> A similar definition can be made for `either-case'. > > > Can you explain the purpose of this? If you have code that consumes a Maybe, you can use `maybe-if' to turn different data paths (encoded by Maybe) into different code paths. But when you also need to know the payload, you would use `maybe-case': (maybe-case maybe ((just x) ;; maybe is a just and `x' is bound to the payload, which must be a single object in this case ...) ((nothing) ;; maybe is a nothing ...)) Marc