-
-
Notifications
You must be signed in to change notification settings - Fork 96
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
initial #:rest-star support #623
Conversation
Needs more tests, and probably some code cleanup now that things appear to be working. |
I remember a previous pull request by @takikawa about this used a (All (K V) (->* [(HashTable K V)] [] #:rest@ (EvenList K V) (HashTable K V))) Where (define-type (EvenList X Y)
(U Null (List* X Y (EvenList X Y)))) This is more flexible because it's not limited to simple repeating cycles, as long as you could write the type of the list with some kind of recursive type you could do it. I found the old pull request on the racket repository, it was made before the packages were split into separate repositories: |
Yes, thanks for linking to that PR: there are definitely more general approaches than what is contained herein. This PR is ready today (i.e. for the next release likely, if we want). A more general approach could likely borrow from what @takikawa put together and---if it is useful---the contents of this PR, but wouldn't be fully ready for a while (I assume). The more general approach still looks like it has some important unknowns:
Out of curiosity, can we think of any rest argument patterns in general use that can't be expressed with a simple cycle rest arg (edit added) and a case->? This PR requires the cycle be complete (i.e. the provided args modulo the cycle length must equal 0) -- it would be easy to also allow incomplete cycles as well. Anyway -- I don't have strong feelings either way. If we want to add this (and maybe incomplete cycles) now I'm happy to do that. If we want to do something more general, I'm happy to scrap this as well. The real benefit of this PR was the bug fixes and cleaning up of |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1 for merging this
I vote we re-use (redacted)#:rest
... so #:rest Integer
is now a synonym for #:rest [Integer]
Command-line flags is a nice example of non-simple cycles. Something like:
(Rec (T)
(U (List 'noarg-flag T)
(List 'arg-flag Integer T)
(Listof String)))
(a b a b a b a b . -> . (-Immutable-HT a b))))] | ||
[hash (-poly (a b) (->* (list) (make-Rest (list a b)) (-Immutable-HT a b)))] | ||
[hasheqv (-poly (a b) (->* (list) (make-Rest (list a b)) (-Immutable-HT a b)))] | ||
[hasheq (-poly (a b) (->* (list) (make-Rest (list a b)) (-Immutable-HT a b)))] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎉
@@ -324,6 +324,8 @@ | |||
,(and rest (type->sexp rest)) | |||
(list ,@(map type->sexp kws)) | |||
,(type->sexp rng))] | |||
[(Rest: tys ) | |||
`(make-Rest (list ,@(map type->sexp tys)))] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(list ,@
can just be ,
right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no, we need the explicit list there since this is hackily generating syntax (otherwise the first type sexp would be in operator position instead of list
in output)
@@ -175,20 +176,24 @@ | |||
;; of them. | |||
(struct seq (types end) #:transparent) | |||
(struct null-end () #:transparent) | |||
(struct uniform-end (type) #:transparent) | |||
(define -null-end (null-end)) | |||
;; cycle is the pattern of the rest of the seq, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
cycle
is a (listof Type?)
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
post 6.11 thought: cycle
is a weird name to me for this "zero-or-more spec".
(like, I wouldn't call #rx"a*"
or (pattern (x:id ...))
"cycles")
How about renaming to star-end
--- or any name that would still make sense if we later add a "one-or-more"-end
struct or other regexp operators
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
star-end
is nice - cool
|
||
[((seq ss (dotted-end s-dty dbound)) | ||
(seq ts (uniform-end t-rest))) | ||
(seq ts (cycle-end (list t-rest-ty)))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
are you sure you don't want to keep the uniform-end
API?
(define (uniform-end t) (cycle-end (list t)))
[new-T (match T-var | ||
[(? Type? var-t) (list-extend S T var-t)] | ||
[(Rest: rst-ts) | ||
#:when (eqv? 0 (remainder fewer-ts (length rst-ts))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess it's safe to use eqv?
here, but:
Welcome to Racket v6.10.0.3.
> (remainder 2.0 2.0)
0.0
> (zero? (remainder 2.0 2.0))
#t
> (= 0 (remainder 2.0 2.0))
#t
> (eqv? 0 (remainder 2.0 2.0))
#f
if this is about speed, why not use eq?
or unsafe-fx=
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there something wrong with eqv? It just seemed like the sensible comparator here to me ¯\_(ツ)_/¯. I don't need all the expresiveness zero?
gives by any stretch, so I just grabbed good ol faithful eqv?
I'd rather not use unsafe-fx=
in code that could easily change.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's nothing technically wrong as long as remainder
s always returning exact integers.
(with-arity (length (attribute mand.doms)) | ||
(define doms (for/list ([d (attribute mand.doms)]) | ||
(parse-type d))) | ||
(define opt-doms (for/list ([d (attribute opt.doms)]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why aren't doms
and opt-doms
also using stx-map
?
EDIT: ok, either (1) why don't they default to #'null
or (2) why not use map
or in-list
?
(cond | ||
[(< (length hrest-tys) 2) | ||
(parse-error | ||
"heterogeneous rest specifications must include at least 2 types" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why "must include at least 2" ?
[(Rest: rst-tys) | ||
(define rst-len (length rst-tys)) | ||
(for/list ([idx (in-range extra-arg-count)]) | ||
(list-ref rst-tys (remainder idx rst-len)))] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why not:
(for/list ([_ (in-range extra-arg-count]
[ty (in-cycle rst-tys)])
ty)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
good point! But in-cycle
super slow dynamic dispatch yuckiness... so I wrote an in-list-cycle
that is significantly faster and achieves the same purpose. Now it's efficient and simple =)
The only sequence we're ever working with is lists anyways, so that should be a potentially useful macro elsewhere as well.
w.r.t. the rest arg potions you described for command-line flags, I think that should expressible with just a |
Can you say more about what you mean by "I vote we re-use I don't understand how we could make that change and not break some parsing we're currently doing (i.e. we parse that expression as a type currently... how can we tell if we should parse it as a type (the old way) or parse it as a sequence of types (the suggested new way). Perhaps I'm missing something to what you meant, though. |
I don't think a
EDIT I take it back, didn't think through this enough. Just use In terms of syntax-parse 😄 the old rest pattern (IIUC) is something like
I'm suggesting the new way be:
|
Just to be clear, I said a Sorry if I'm coming across as overly pedantic for the sake of pedantry -- that's not my intent. I worry (perhaps too much) in the past it has been unclear or we have confused details about our functions and their types, and so I'm just trying to be extra clear about what we mean. Yes, I see now. If we wanted to have a rest type that is expressing some arbitrary ordering of sequences of arguments, a la: (Rec T (U (List* 'noarg-flag T)
(List* 'arg-flag Integer T)
(Listof String))) We could not do that with just a cycle. But certainly we could write a function that accepts the input and then does argument checking internally. I suspect that even with general rest types, some of the restrictions you would ideally want (i.e. probably no duplicates of certain flags, etc) will not be expressible (at least any time soon) with a richer rest argument type. Anyway, ramble ramble ramble. Sorry for being long winded -- thanks for the real world example! =) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A few initial comments.
;; a Rest argument description | ||
;; tys: the cycle describing the rest args | ||
;; e.g. | ||
;; tys = (list Number) means all provided rest args |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this have to be (list T)
instead of T
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
that makes handling any rest args (i.e. uniform or more interesting cycles) all able to use the same code
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, I see now
@@ -1,13 +1,13 @@ | |||
#; | |||
(exn-pred 3) | |||
(exn-pred 2) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why did this change?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was a silly mistake on my part. The file still errors w/ 3 type checker errors (I had changed the file, forgot I changed it, and mindlessly updated the exn-pred line (doh!))
It's fixed now. Same behavior as before
[(:->*^ (~var mand (->*-args #t)) | ||
(~optional (~var opt (->*-args #f)) | ||
#:defaults ([opt.doms null] [opt.kws null])) | ||
rest:->*-rest | ||
#:rest* (rest-types-stx:non-keyword-ty ...) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are there any tests/docs for this new form?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no -- wanted feedback on what it should actually look like for user syntax first.
I don't want to just re-use #:rest
because I worry that will lead to ambiguous parsing, but I don't have strong feelings on what to use instead. rest*
as in Kleene-star seems sensible to me. I dunno.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#:rest*
seems fine, or #:rest-sequence
or perhaps something else?
Update! Things are working well it seems. I believe all that is left before possibly being ready to merge is the following:
|
I think it's ready(ish) for further review... possibly merging? Hard to know. All the bullet points I had wanted to address have at least been addressed to some degree. |
I think this is good to go. |
Can we get this merged? |
This PR adds support for rest specifications that are the Kleene closure of some sequence.
e.g. functions like
hash
orhash-set*
which require a cyclic rest argument with alternating key and value types will be able to be specified with something like the following:Thanks for comments/suggestions from @AlexKnauth, @bennn, and @samth; also thanks to @takikawa for previous work exploring some of these features.
If we later at more general rest argument support, this will simply be subsumed by that and hopefully add some tests and use cases that can help prove a more general implementation is correct and behaves as expected.