*func main() { for x := range FSeq(strconv.Atoi) {
fmt.Println(reflect.TypeOf(x)) }}func FSeq[V (... any)](f
func(string) V) iter.Seq[V] { return func(yield func(v V) bool)) {
yield(f("1234")) }}*
*>>> If it wouldn't be valid, why not? If it's OK, what would it print?>>
my pedantry would suggest spelling out the semantics `type R (Value int,
Err error)` and `FSeq[R](strconv.Atoi)`.> I don't really understand how
that could work, tbh. In `FSeq[R]` it seems like you're passing a single
type parameter of type R, so the V type parameter would have a single
member of type R. But then that arity (1) wouldn't match the arity of the
argument count to strconv (2).> That is, given `FSeq[R]`, I'd expect it to
take an argument of type `func(string) R` not `func(string) (int, error)`
but if that's the case, the example wouldn't work AFAICS.> It seems to me
that this conflation of sometimes-named tuples and value-lists is a
significant flaw in the suggested semantics.*
Formally, I'd argue `R` is a valid instantiation of the tuple constraint
`(... any)`. `(... any)` is the entire universe of tuples, and `R` is a
tuple type. Additionally, Go already recognizes the two tuples in a
signature
(https://github.com/golang/go/blob/master/src/go/types/signature.go) `func
(string) (int, error)` on a more implicit basis.
There is an unstated principle that follows from a notion of promotion. If
unnamed->named tuple type promotion is free and automatic, it makes sense
to freely unify any occurrences of implicit tuple type `(int, error)` with
`R`, but not to unify two congruent named types. I'd thought about this,
but a quirk I didn't consider is that a type parameter might unify to a
named type in composition, but use an implicit type at the point of
instantiation - I think it's justifiable to just promote down to the
implicit type in this case.
I wonder that this might be a question that #64457 would run into as well,
if it were open, because it also allowed named tuple types.
---
A tangent on what's formally possible, currently:
https://go.dev/play/p/7JRCfkjNpJk
We can write:
- A function `F`
- that accepts as input, a function `f`
- accepting 1 string and returning 2 things (`T`),
- parameteric over they types of 2 things (`K` and `V`)
- and prints the structural signature type.
This does parameterize on a single type `T`, with some additional
complication bridging (3) and (4): a type `KV`, itself parametrized on `K`
and `V`, leveraging the fact that tuples already exist in the type system
as second-class components of function signatures.
Without tuple constraints:
1. `func F[T KV[K, V], K any, V any](f T)`
With tuple constraints:
2. `func F[T (any, any)](f func(string) T)` // not variadic
3. `func F[Out (... any)](f func(string) Out)` // variadic tuple
constraint; this matches the formal limit of #66651
4. `func F[In (... any), Out (... any)](f func(In) Out)` multiple variadic
tuple constraints
On Tuesday, April 30, 2024 at 8:12:09 AM UTC-7 roger peppe wrote:
> On Mon, 29 Apr 2024 at 22:06, Andrew Harris <harris...@gmail.com> wrote:
>
>> 2. On implementation, before considering variadic tuple constraints: There
>> is tuple machinery already in the type system
>> <https://github.com/golang/go/blob/master/src/go/types/tuple.go>, used
>> for assignments and signatures, but tuples are not first class. Also, we
>> know where and how they fit in the unification algorithm
>> <https://github.com/golang/go/blob/16ce8b3925deaeb72541ee96b6ee23a08fc21dea/src/go/types/unify.go#L629>.
>> It's
>> as simple as one could hope: *match the arity, then unify the component
>> types*. I take this as evidence the idea of a tuple constraint is
>> feasible.
>>
>
> Note added emphasis above.
>
>
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
>> *func main() { for x := range FSeq(strconv.Atoi) {
>> fmt.Println(reflect.TypeOf(x)) }}func FSeq[V (... any)](f
>> func(string) V) iter.Seq[V] { return func(yield func(v V) bool)) {
>> yield(f("1234")) }}*
>>
>>
>> *If it wouldn't be valid, why not? If it's OK, what would it print?*
>>
> my pedantry would suggest spelling out the semantics `type R (Value int,
>> Err error)` and `FSeq[R](strconv.Atoi)`.
>
>
> I don't really understand how that could work, tbh. In `FSeq[R]` it seems
> like you're passing a single type parameter of type R,
> so the V type parameter would have a single member of type R. But then
> that arity (1) wouldn't match the arity of the
> argument count to strconv (2).
>
> That is, given `FSeq[R]`, I'd expect it to take an argument of type
> `func(string) R` not `func(string) (int, error)`
> but if that's the case, the example wouldn't work AFAICS.
>
> It seems to me that this conflation of sometimes-named tuples and
> value-lists is a significant flaw in the suggested semantics.
>
> cheers,
> rog.
>
> On Mon, 29 Apr 2024 at 22:06, Andrew Harris <harris...@gmail.com> wrote:
>
>>
>> *> FWIW this kind of restriction has no precedent in Go. Currently any
>> type may be unnamed. This restriction seems arbitrary and irregular to me. *
>> It seems unpopular.
>>
>>
>> *> Isn't this illegal according to your TupleType syntax above? *
>> Further evidence of a wrong idea :)
>>
>>
>>
>>
>> *>> [T (any, any)] describes the type set consisting of any 2-ary tuple>
>> By analogy with [T map[any]any] I'd expect the "any" there to be the
>> regular "any" type (as used outside generics) rather than the type
>> constraint "any". That is, my intuition would expect this to disallow a
>> (string, string) tuple, but my sense is that that's not what you're
>> suggesting. In general, a type constraint can be either a regular type (a
>> type set) or an interface type, but this doesn't seem like either, and
>> looks to me like it would significantly complicate the generics
>> specification.*
>> Yes - I'm suggesting a third category of constraint. It would be a
>> significant change, and a tradeoff that would add weight to the generics
>> spec. To further detail what I think this involves:
>>
>> 1. The amount of syntax is on the same order as specifying tuples for
>> non-generic code. Introducing tuples is pretty efficient. I'd imagine a
>> sharp, finely tuned framing the tuple->list-of-types substitution adds more
>> cases to existing syntax, on the same order as #64457 (tuples for go) or
>> #66651 (variadic type parameters) would also require. Overall, #64613 (just
>> `...` for structs) seems simpler.
>>
>> 2. On implementation, before considering variadic tuple constraints: There
>> is tuple machinery already in the type system
>> <https://github.com/golang/go/blob/master/src/go/types/tuple.go>, used
>> for assignments and signatures, but tuples are not first class. Also, we
>> know where and how they fit in the unification algorithm
>> <https://github.com/golang/go/blob/16ce8b3925deaeb72541ee96b6ee23a08fc21dea/src/go/types/unify.go#L629>.
>> It's
>> as simple as one could hope: match the arity, then unify the component
>> types. I take this as evidence the idea of a tuple constraint is feasible.
>>
>> 3. On implementation with variadiac tuple constraints: So long as
>> variadicity is limited to a single, trailing tuple component, the
>> corresponding step in unification - matching arity - is nearly trivial, and
>> unification of types is likewise uncomplicated.
>>
>> 4. Considering varidiac elements of a tuple constraint in positions other
>> than the tail: I don't have a confident analysis of everything that comes
>> to mind, but I'm thinking this leads to bad outcomes, like an increase in
>> the computational complexity of the unification algorithm, or obnoxiously
>> esoteric rules for how constraints intersect.
>>
>>
>>
>> *>> [T (K, any), K comparable] describes the type set of 2-ary tuples
>> that begin with a comparable element.> How would you see that as different
>> from [T (comparable, any)] ?*
>>
>> The type parameter `K` might be significant elsewhere, e.g. `func F[T (K,
>> any), K any](f func(T), input K)`.
>>
>>
>>
>> *> Would these two be equivalent too?> func F[K comparable, V any](f
>> []func(K, V)) {}> func F[KV (comparable, any)](f []func(KV)) {}*
>>
>> Yes, and I can be sharper about "equivalent". I don't think that the type
>> parameter lists should be equivalent. Tuple constraints capture essential
>> information about grouping and position of types. But any
>> slice-of-functions type should be equivalently valid or invalid for either.
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
>> *func main() { for x := range FSeq(strconv.Atoi) {
>> fmt.Println(reflect.TypeOf(x)) }}func FSeq[V (... any)](f
>> func(string) V) iter.Seq[V] { return func(yield func(v V) bool)) {
>> yield(f("1234")) }}*
>>
>>
>> *If it wouldn't be valid, why not? If it's OK, what would it print?*
>> My understanding is that how helpful type inference can be is not a
>> question of specification. I would hope the code successfully infers
>> without hints, and prints `(int, error)`. If not, by #64457 rules the hint
>> would be `FSeq[(int, error)](strconv.Atoi)`; my pedantry would suggest
>> spelling out the semantics `type R (Value int, Err error)` and
>> `FSeq[R](strconv.Atoi)`. (I'm losing conviction on that pedantry...)
>> On Monday, April 29, 2024 at 2:24:46 AM UTC-7 roger peppe wrote:
>>
>>> On Sun, 28 Apr 2024 at 12:10, Andrew Harris <harr...@spu.edu> wrote:
>>>
>>>> Bouncing out from some recent discussions on the github issue tracker,
>>>> it seems like there's some interest in tuples in Go. I thought the
>>>> discussion in #66651 led to some interesting ideas, but it's also
>>>> beginning
>>>> to drift. Maybe this is a better place to brain-dump some ideas. (This
>>>> could be a proposal but I'm not sure that's quite right either, that might
>>>> be spammy.)
>>>>
>>>> Some recent issues:
>>>> 1. #64457 "Tuple types for Go"
>>>> <https://github.com/golang/go/issues/64457> (@griesemer)
>>>> 2. #66651 "Variadic type parameters"
>>>> <https://github.com/golang/go/issues/66651> (@ianlancetaylor)
>>>> 3. "support for easy packing/unpacking of struct types"
>>>> <https://github.com/golang/go/issues/64613> (@griesemer)
>>>>
>>>> Synthesizing from those discussions, and satisfying requirements
>>>> framed by @rogpeppe
>>>> <https://github.com/golang/go/issues/66651#issuecomment-2054198677>,
>>>> the following is a design for tuples that comes in two parts. The first
>>>> part explores tuples in non-generic code, resembling a restrained version
>>>> of #64457. The second part explores tuple constraints for generic code,
>>>> reframing some ideas from #66651 in terms of tuples. It's a fungal kingdom
>>>> approach, where tuples occupy some unique niches but aren't intended to
>>>> dominate the landscape.
>>>>
>>>> *TUPLES IN NON-GENERIC CODE*
>>>>
>>>> Tuples are evil
>>>> <https://github.com/golang/go/issues/32941#issuecomment-509367113> because
>>>> the naming schemes are deficient. To enjoy greater name abundancy, this
>>>> design tweaks tuple *types* from #64457 in the direction of
>>>> "super-lightweight
>>>> structs"
>>>> <https://github.com/golang/go/issues/64457#issuecomment-1834358907>.
>>>> It still allows tuple *expressions* from #64457, for tuples
>>>> constructed from bare values.
>>>>
>>>> *1. Tuple types*
>>>> Outside of generics, tuple *type* syntax requires named fields.
>>>>
>>>> TupleType = "(" { IdentifierList Type [ ", " ] } ")" .
>>>>
>>>> // e.g.:
>>>> type Point (X, Y int)
>>>>
>>>> More irregularly, the TupleType syntax is used *exclusively* to
>>>> declare named types, and these named tuple types cannot implement methods.
>>>> As a result, a named tuple type is entirely defined at the site of the
>>>> type
>>>> definition.
>>>>
>>>
>>> FWIW this kind of restriction has no precedent in Go. Currently any type
>>> may be unnamed. This restriction seems arbitrary and irregular to me.
>>>
>>>>
>>>> *2. Tuple literals*
>>>> The tuple *expression* syntax of #64457 remains valid. The result is
>>>> an implicitly typed tuple value. Literals of a named tuple type are also
>>>> valid, and resemble struct literals.
>>>>
>>>> point1 := (0, 0) // implicitly typed
>>>> point2 := Point(X: 0, Y: 0) // explicitly typed
>>>>
>>>>
>>>> *3. Promotion and expansion*
>>>> There is no way to capture the type of an implicitly typed tuple value
>>>> - the result of a bare tuple *expression* - with tuple *type* syntax.
>>>> However, promotion and expansion are available as way to leverage tuple
>>>> values.
>>>>
>>>> - Promotion: An implicitly typed tuple value is freely and
>>>> automatically promoted to a value of a named tuple type, if and only if
>>>> the
>>>> sequence of types is congruent (same types, same order, same arity)
>>>> between
>>>> the implicit and named type:
>>>>
>>>> type T (string, string)
>>>>
>>> Isn't this illegal according to your TupleType syntax above?
>>>
>>>> var t T
>>>> t := ("foo", "bar")
>>>>
>>>> The RHS of the assignment is implicitly typed (string, string), so the
>>>> value can be promoted to the LHS's congruent type T without further
>>>> ceremony.
>>>>
>>>> - Any tuple value can, under the condition of congruence, expand with
>>>> ... "wherever a list of values is expected" (#66651). This means
>>>> places like assignments, function calls, function returns,
>>>> struct/slice/array literals, for/range loops, and channel receives. Each
>>>> of
>>>> the github issues (#64457, #64613, #66651) explores this in more detail.
>>>> Qualifications and some subjectivity are involved, and a full proposal
>>>> would explore this more completely and sharply, but the intuitive notion
>>>> is
>>>> pretty straightforward.
>>>>
>>>>
>>>> *TUPLE CONSTRAINTS*
>>>> For generic code, this design's driving concept is tuple constraints. A
>>>> tuple constraint describes type sets that are exclusively composed of
>>>> tuple
>>>> types. Loosely, where union-of-types or set-of-methods type constraints
>>>> are
>>>> currently, a tuple constraint would also be allowed. The rules for code
>>>> parameterized on tuple constraints should resemble #66651 in many ways.
>>>> Most essentially, it should be possible to substitute a tuple constraint
>>>> "wherever a list of types is permitted", as suggested in #66651.
>>>>
>>>>
>>>> *1. Non-variadic tuple constraints*
>>>> The current TypeParamDecl production is:
>>>>
>>>> TypeParamDecl = IdentifierList TypeConstraint .
>>>>
>>>> Adding tuple constraints can be accomplished by extending TypeParamDecl
>>>> syntax
>>>> to include an alternative to the TypeConstraint, a TupleConstraint.
>>>> Then, a tuple constraint is constructed from TypeConstraint elements.
>>>>
>>>> TypeParamDecl = IdentifierList ( TypeConstraint | TupleConstraint ) .
>>>> TupleConstraint = "(" { TypeConstraint [ "," ] } ")" .
>>>>
>>>> Some examples:
>>>> [T (any, any)] describes the type set consisting of any 2-ary tuple
>>>>
>>>
>>> By analogy with [T map[any]any] I'd expect the "any" there to be the
>>> regular "any" type (as used outside generics) rather than the type
>>> constraint "any". That is, my intuition would expect this to disallow a
>>> (string, string) tuple, but my sense is that that's not what you're
>>> suggesting.
>>>
>>> In general, a type constraint can be either a regular type (a type set)
>>> or an interface type, but this doesn't seem like either, and looks to me
>>> like it would significantly complicate the generics specification.
>>>
>>>
>>>> [T (K, any), K comparable] describes the type set of 2-ary tuples that
>>>> begin with a comparable element.
>>>>
>>>
>>> How would you see that as different from [T (comparable, any)] ?
>>>
>>>
>>>>
>>>> Via tuple -> list-of-types substitution, the following would be
>>>> equivalent:
>>>>
>>>> func F[K comparable, V any](f func(K, V)) { ... }
>>>> func F[KV (comparable, any)](f func(KV)) { ... }
>>>
>>>
>>> Would these two be equivalent too?
>>>
>>> func FK comparable, V any](f []func(K, V)) {}
>>>
>>> func F[KV (comparable, any)](f []func(KV)) {}
>>>
>>>> *2. Variadic tuple constraints*
>>>>
>>>> A variadic tuple constraint is described with an extension to the
>>>> TupleConstraint production: an optional VariadicTupleElement is
>>>> appended to it.
>>>>
>>>> TupleConstraint = "(" { TypeConstraint [ "," ] } [ VariadicTupleElement
>>>> ] ")" .
>>>> VariadicTupleElement = "..." TypeConstraint .
>>>>
>>>> The identifier for a variadic tuple constraint may be still be
>>>> substituted for a list of types. Drawing from use cases discussed in
>>>> #66651, this leads to function signatures like:
>>>>
>>>> func Filter[V (... any)](f func(V), seq Seq[V]) Seq[V]
>>>>
>>>> func MergeFunc[V (... any)](xs, ys Seq[V], f func(V, V) int) Seq[V]
>>>>
>>>
>>> It sounds like by your explanation here that this code should be valid:
>>>
>>> func main() {
>>> for x := range FSeq(strconv.Atoi) {
>>> fmt.Println(reflect.TypeOf(x))
>>> }
>>> }
>>>
>>> func FSeq[V (... any)](f func(string) V) iter.Seq[V] {
>>> return func(yield func(v V) bool)) {
>>> yield(f("1234"))
>>> }
>>> }
>>>
>>> If it wouldn't be valid, why not? If it's OK, what would it print?
>>>
>> --
>>
> You received this message because you are subscribed to the Google Groups
>> "golang-nuts" group.
>> To unsubscribe from this group and stop receiving emails from it, send an
>> email to golang-nuts...@googlegroups.com.
>>
> To view this discussion on the web visit
>> https://groups.google.com/d/msgid/golang-nuts/a7482fb7-a130-4ba7-aad6-9f81910e7b95n%40googlegroups.com
>>
>> <https://groups.google.com/d/msgid/golang-nuts/a7482fb7-a130-4ba7-aad6-9f81910e7b95n%40googlegroups.com?utm_medium=email&utm_source=footer>
>> .
>>
>
--
You received this message because you are subscribed to the Google Groups
"golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to golang-nuts+unsubscr...@googlegroups.com.
To view this discussion on the web visit
https://groups.google.com/d/msgid/golang-nuts/d866827a-4ef2-4576-950a-1ffb35f2fdffn%40googlegroups.com.