Re: [go-nuts] Generics: Whats the best way around limitation that methods can't be parameterized
On Thu, 2022-01-20 at 19:05 -0800, Mandolyte wrote: > Or perhaps a hybrid, where the methods call generic functions... > > On Thursday, January 20, 2022 at 7:23:37 PM UTC-5 Ian Lance Taylor > wrote: > > On Thu, Jan 20, 2022 at 3:30 AM Travis Keep > > wrote: > > > > > > I am working on a Pipeline[T, U any] type which receives any > > number of T values and in turn emits U values. Pipelines are used > > to read from databases. > > > > > > For instance, this code fetches the ages of 5 people under 50. > > > > > > int[] ages > > > PeopleOrderedByName(Start[Person](). > > > Map(func(p Person) int { return p.Age }). > > > Filter(func(age int) bool { return age < 50 }). > > > Slice(0, 5). > > > AppendTo()) > > > > > > // Output: {49, 19, 25, 34, 42} > > > fmt.Println(ages) > > > > > > Some notes about this code: > > > > > > 1. Start[Person]() returns a Pipeline[Person, Person] that emits > > each Person value it receives. > > > 2. the Map method on Pipeline is parameterized. What Map returns > > depends on the return value of the mapper function passed in. In > > the example above, Map() returns a Pipeline[Person, int] > > > 3. AppendTo() returns a Consumer[Person] which takes Person > > values without emitting anything. As a side effect, this > > Consumer[Person] appends the ages of people under 50 to the ages > > slice. > > > 4. Even if there are millions of people in the database, this > > code will read just enough people to get 5 ages under 50. > > > 5. This code won't work in 1.18 because methods in GO on a > > parameterized type like Pipeline cannot be further parameterized, > > but in this API, the Map() method has to be parameterized. > > > > > > To get around this limitation, I revised my API to look like > > this: > > > > > > int[] ages > > > PeopleOrderedByName( > > > SendTo[Person, int]( > > > Start(). > > > Map(func(person interface{}) interface{} { return > > person.(Person).Age }). > > > Filter(func(age interface{}) bool { return age.(int) < 50 }). > > > Slice(0, 5), > > > AppendTo()) > > > > > > // Output: {49, 19, 25, 34, 42} > > > fmt.Println(ages) > > > > > > Notes about this API: > > > > > > 1. Pipeline is no longer a parameterized type. Pipeline instances > > receive interface{} values and emit interface{} values. > > > 2. AppendTo() returns a Consumer[int] that appends all the > > values it receives to the ages slice. > > > 3. SendTo[Person, int]() returns a Consumer[Person] that > > applies a pipeline to the Person values it receives and then sends > > the emitted values to the Consumer[int] passed as a second value. > > > 4. This API suffers all the disadvantages that generics aims to > > solve. The pipeline code is clunkier because values have to be > > constantly converted from interface{}, Problems with type > > mismatches become runtime errors instead of compile time errors, > > storing a Person value in an interface results in an extra memory > > allocation etc. > > > > > > Is there a more elegant way around the limitation that methods on > > parameteried types can't be parameterized? > > > > > > I don't know about "more elegant" but I would suggest not trying > > to > > use a fluent API. Use a function based API instead. Maybe it would > > look like > > > > p := pipelinePeopleOrderedByName(Start[Person]() > > p = pipeline.Map(p, func(p Person) int { return p.Age }) > > p = pipeline.Filter(p, func(age int) bool { return age < 50 }) > > p = pipeline.Slice(p, 0, 5) > > ages = append(ages, pipeline.Results(p)) > > > > (Of course I've omitted the error handling, but your example did as > > well.) > > > > Ian I saw a post at JBD's blog about making this easier: https://rakyll.org/generics-facilititators/ -- 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/7c8eba6ce27db310daec8c3a34d53cb8c233ddd6.camel%40kortschak.io.
Re: [go-nuts] Generics: Whats the best way around limitation that methods can't be parameterized
Or perhaps a hybrid, where the methods call generic functions... On Thursday, January 20, 2022 at 7:23:37 PM UTC-5 Ian Lance Taylor wrote: > On Thu, Jan 20, 2022 at 3:30 AM Travis Keep wrote: > > > > I am working on a Pipeline[T, U any] type which receives any number of T > values and in turn emits U values. Pipelines are used to read from > databases. > > > > For instance, this code fetches the ages of 5 people under 50. > > > > int[] ages > > PeopleOrderedByName(Start[Person](). > > Map(func(p Person) int { return p.Age }). > > Filter(func(age int) bool { return age < 50 }). > > Slice(0, 5). > > AppendTo()) > > > > // Output: {49, 19, 25, 34, 42} > > fmt.Println(ages) > > > > Some notes about this code: > > > > 1. Start[Person]() returns a Pipeline[Person, Person] that emits each > Person value it receives. > > 2. the Map method on Pipeline is parameterized. What Map returns depends > on the return value of the mapper function passed in. In the example above, > Map() returns a Pipeline[Person, int] > > 3. AppendTo() returns a Consumer[Person] which takes Person values > without emitting anything. As a side effect, this Consumer[Person] appends > the ages of people under 50 to the ages slice. > > 4. Even if there are millions of people in the database, this code will > read just enough people to get 5 ages under 50. > > 5. This code won't work in 1.18 because methods in GO on a parameterized > type like Pipeline cannot be further parameterized, but in this API, the > Map() method has to be parameterized. > > > > To get around this limitation, I revised my API to look like this: > > > > int[] ages > > PeopleOrderedByName( > > SendTo[Person, int]( > > Start(). > > Map(func(person interface{}) interface{} { return person.(Person).Age }). > > Filter(func(age interface{}) bool { return age.(int) < 50 }). > > Slice(0, 5), > > AppendTo()) > > > > // Output: {49, 19, 25, 34, 42} > > fmt.Println(ages) > > > > Notes about this API: > > > > 1. Pipeline is no longer a parameterized type. Pipeline instances > receive interface{} values and emit interface{} values. > > 2. AppendTo() returns a Consumer[int] that appends all the values > it receives to the ages slice. > > 3. SendTo[Person, int]() returns a Consumer[Person] that applies a > pipeline to the Person values it receives and then sends the emitted values > to the Consumer[int] passed as a second value. > > 4. This API suffers all the disadvantages that generics aims to solve. > The pipeline code is clunkier because values have to be constantly > converted from interface{}, Problems with type mismatches become runtime > errors instead of compile time errors, storing a Person value in an > interface results in an extra memory allocation etc. > > > > Is there a more elegant way around the limitation that methods on > parameteried types can't be parameterized? > > > I don't know about "more elegant" but I would suggest not trying to > use a fluent API. Use a function based API instead. Maybe it would > look like > > p := pipelinePeopleOrderedByName(Start[Person]() > p = pipeline.Map(p, func(p Person) int { return p.Age }) > p = pipeline.Filter(p, func(age int) bool { return age < 50 }) > p = pipeline.Slice(p, 0, 5) > ages = append(ages, pipeline.Results(p)) > > (Of course I've omitted the error handling, but your example did as well.) > > Ian > -- 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/163d0bd5-a50f-45d0-96bb-1129685a5037n%40googlegroups.com.
Re: [go-nuts] Generics: Whats the best way around limitation that methods can't be parameterized
On Thu, Jan 20, 2022 at 3:30 AM Travis Keep wrote: > > I am working on a Pipeline[T, U any] type which receives any number of T > values and in turn emits U values. Pipelines are used to read from databases. > > For instance, this code fetches the ages of 5 people under 50. > > int[] ages > PeopleOrderedByName(Start[Person](). > Map(func(p Person) int { return p.Age }). > Filter(func(age int) bool { return age < 50 }). > Slice(0, 5). > AppendTo()) > > // Output: {49, 19, 25, 34, 42} > fmt.Println(ages) > > Some notes about this code: > > 1. Start[Person]() returns a Pipeline[Person, Person] that emits each Person > value it receives. > 2. the Map method on Pipeline is parameterized. What Map returns depends on > the return value of the mapper function passed in. In the example above, > Map() returns a Pipeline[Person, int] > 3. AppendTo() returns a Consumer[Person] which takes Person values > without emitting anything. As a side effect, this Consumer[Person] appends > the ages of people under 50 to the ages slice. > 4. Even if there are millions of people in the database, this code will read > just enough people to get 5 ages under 50. > 5. This code won't work in 1.18 because methods in GO on a parameterized type > like Pipeline cannot be further parameterized, but in this API, the Map() > method has to be parameterized. > > To get around this limitation, I revised my API to look like this: > > int[] ages > PeopleOrderedByName( > SendTo[Person, int]( > Start(). > Map(func(person interface{}) interface{} { return person.(Person).Age > }). > Filter(func(age interface{}) bool { return age.(int) < 50 }). > Slice(0, 5), > AppendTo()) > > // Output: {49, 19, 25, 34, 42} > fmt.Println(ages) > > Notes about this API: > > 1. Pipeline is no longer a parameterized type. Pipeline instances receive > interface{} values and emit interface{} values. > 2. AppendTo() returns a Consumer[int] that appends all the values it > receives to the ages slice. > 3. SendTo[Person, int]() returns a Consumer[Person] that applies a > pipeline to the Person values it receives and then sends the emitted values > to the Consumer[int] passed as a second value. > 4. This API suffers all the disadvantages that generics aims to solve. The > pipeline code is clunkier because values have to be constantly converted from > interface{}, Problems with type mismatches become runtime errors instead of > compile time errors, storing a Person value in an interface results in an > extra memory allocation etc. > > Is there a more elegant way around the limitation that methods on > parameteried types can't be parameterized? I don't know about "more elegant" but I would suggest not trying to use a fluent API. Use a function based API instead. Maybe it would look like p := pipelinePeopleOrderedByName(Start[Person]() p = pipeline.Map(p, func(p Person) int { return p.Age }) p = pipeline.Filter(p, func(age int) bool { return age < 50 }) p = pipeline.Slice(p, 0, 5) ages = append(ages, pipeline.Results(p)) (Of course I've omitted the error handling, but your example did as well.) Ian -- 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/CAOyqgcVzTJXXkOL0sGRjTDv9vt6%2B-cf9s7RzCDnuFe9JFCsRyg%40mail.gmail.com.