Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

I think I see what you're doing. Right now, each result type implements NextPager, which returns information about how to fetch the next page. You client can implement a utility like FetchNextPage:

    type NextPager interface {
        NextPage() PageSpec
    }
    func (c *Client) FetchNextPage(ctx context.Context, current NextPager) (interface{}, error) {
        ...
    }
Then for each type of paged object, you write:

   func (c *FooClient) FetchNextFoo(ctx context.Context, current Foo) (Foo, error) {
       next, err := c.client.FetchNextPage(ctx, current)
       ...
       if n, ok := next.(Foo); ok {
           return n, nil
       }
       return Foo{}, fmt.Errorf("unexpected type: got %T, want Foo", next)
   }
That's annoying. But, this problem has come up before with `sql`, which has rows.Next() and rows.Scan() to iterate over arbitrary row types, and you could use that as a model:

    pages := client.Query(...)
    defer pages.Close()
    for pages.Next() {
        var foo Foo
        if err := pages.Scan(&foo); err != nil { ... }
        // do something with the page
    }
    
Generics would let you enforce the type of `foo` at compile time, but it wouldn't save you many lines of code. I think you still have to write (or generate) a function like `func (c *Client) ListFoos(ctx context.Context, req ListFooRequest) (Paged[Foo], error) { ... }`. We hand-wave over that in the above example with a "..." passed to query (potentially possible if you retrive objects with a stringified query, like SQL or GraphQL), but that sounds like the hard and tedious part.

Let me conclude with a recommendation for gRPC and gRPC-gateway as a bridge to clients that don't want to speak gRPC. Then you can just return a "stream Foo", and the hard work is done for you. You call stream.Next() and get a Foo object ;)



> Generics would let you enforce the type of `foo` at compile time, but it wouldn't save you many lines of code.

You literally showed that "for each type of paged object, you write <multiple lines of entirely unnecessary code>".

Where with generics you just have a single generic function.

> We hand-wave over that in the above example with

The problem is: there's no hand-waving in reality.


I'm traveling, so can't properly expand on what I said, but without generics the choice was:

- repeat unnecessary bolierplate code for every single return type

- skip types completely and use a function that accepts interface{} as a parameter and assigns whatever to that variable

Neither are really acceptable in my opinion. And both will be greatly simplified and improved by generics.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: