Live data from Hacker News

Reusable and type-safe options for Go APIs

derekchiang.com

21–30 of 36 posts

Re: Reusable and type-safe options for Go APIs

#21
post #18

How is it not cleaner and simpler to just use something of a builder pattern? srv = NewServer(addr).WithTLS(crt,key).WithRateLimit(30).WithPool(10).Start() if srv.Err() { // Handle }

In Go, chaining is not idiomatic. For one, it becomes more challenging to determine where the error occurred if you want to try to handle the error instead of failing. Also, it requires each function in the chain to detect if an error has already occurred. Interfaces are generally a better solution.

Clearly a fluid API can not return a 2+ tuple as its results so the error issue you note is a non-issue.

The clean way to deal with errors in fluid APIs is to have a terminal .init() that returns an error (which can also specifically note which options are "illegal", etc.)

Re: Reusable and type-safe options for Go APIs

#22
post #8
post #3

Once you accept that the golang type system can't enforce everything your life will be easier. Use rust or Haskell if you want a powerful type system that can do fancy things. golang is great, but you have to accept that some things are runtime errors, and that's fine, it keeps things simple. How often are options given conditionally anyways? Simple tests will cover this in most cases.

No languages are perfect. Yes Go type system can't enforce everything, but it enforce many things. And Go is still language in evolving.

These are all true things about any language on earth, except perhaps the evolving part.

Re: Reusable and type-safe options for Go APIs

#23
post #13
post #2

I've been using Go as my preferred language for almost a decade now. Hackish "clever" solutions like these coming from designers of the language like Rob Pike and Dace Cheney simply indicate that the language is hitting its limits of expressivity and needs to start thinking hard about overloading which is the right solution to problems like this.

I had the same feeling, and then i tried to implement the same solution in swift (which has more evolved constructs for enums and option sets), but it's actually not easy to do as well, if you want to handle all those constraints at the same time: 1/ a potentially big number of options, without creating functions with a huge list of parameters 2/ options with associated values (revision: int) 3/ reusable options betw…

I realise that you tried to work with the same constraints as the Go solution for the sake of argument.

But the whole rationale for having 3/ in the first place isn't valid in Swift, so it seems to me that the obvious Swift solution is to use an enum per operation.

Re: Reusable and type-safe options for Go APIs

#24
post #18

How is it not cleaner and simpler to just use something of a builder pattern? srv = NewServer(addr).WithTLS(crt,key).WithRateLimit(30).WithPool(10).Start() if srv.Err() { // Handle }

In Go, chaining is not idiomatic. For one, it becomes more challenging to determine where the error occurred if you want to try to handle the error instead of failing. Also, it requires each function in the chain to detect if an error has already occurred. Interfaces are generally a better solution.

[deleted]

Re: Reusable and type-safe options for Go APIs

#25
The downside to this solution is that the concrete option types are exported. I believe it's possible to return unexported types from exported functions, which would solve this, but that golint complains about that pattern.

Have you suggested this to the etcd maintainers?

Re: Reusable and type-safe options for Go APIs

#26

How is it not cleaner and simpler to just use something of a builder pattern? srv = NewServer(addr).WithTLS(crt,key).WithRateLimit(30).WithPool(10).Start() if srv.Err() { // Handle }

How is it cleaner and simpler? It reads about the same to me.

Re: Reusable and type-safe options for Go APIs

#27
post #13

Earlier quoted context omitted.

I had the same feeling, and then i tried to implement the same solution in swift (which has more evolved constructs for enums and option sets), but it's actually not easy to do as well, if you want to handle all those constraints at the same time: 1/ a potentially big number of options, without creating functions with a huge list of parameters 2/ options with associated values (revision: int) 3/ reusable options betw…

I realise that you tried to work with the same constraints as the Go solution for the sake of argument. But the whole rationale for having 3/ in the first place isn't valid in Swift, so it seems to me that the obvious Swift solution is to use an enum per operation.

It does makes quite a bit of sense, especially in the case where all those enums end up being processed by one single private function, such as "configureServer(config: ConfOption) ". (i'm refering to the linked post by Dave cheney here)

I spent a bit more time on the problem, and now i'm pretty sure it is also not solvable "cleanly" in swift, because it actually would need to model some kind of constraints over the accepted values of a type for a parameter.

Something like get(opt: Options... )

Otherwise you'll need to manually create a subenum, and manually compare cases from the original one with the sub one.

Re: Reusable and type-safe options for Go APIs

#28

I find I don't care for implementing the Options paradigm. More often then not, the things I've implemented have required parameters, and sometimes many of them. Mixing required and options often feels kludgy. I feel the Options method requires lots of documentation referencing, as opposed to more discoverable (via your editor) constructors. I tend to use explicit parameters or a struct with validators where every fi…

I have to say I agree. I came to Go from functional languages (primarily Lisp), so you'd think that the "Functional Options for Friendly APIs" pattern would appeal to me, but I've never seem an implementation that I've actually liked to use.

This is a nice implementation from Dave Cheney: https://github.com/pkg/profile

Re: Reusable and type-safe options for Go APIs

#29
post #19
post #5

Earlier quoted context omitted.

Rob Pike and Dave Cheney know way better than me, but I can't help to think they're trying to be "easy rather than simple", here. Rob doesn't explain why a config struct is not good for him in his reference article, and Dave is saying that's because 1/ it needs to be passed even when it's empty and 2/ its zero values may be a trouble, giving the example of explicitly setting `Port` to 0 to let OS selecting first free…

For the zero value problem, a pointer is usually used rather than an "identifier value" like -1. var price *float64 if price == nil { // Handle empty value }

This works if you can guarantee the variable will not ever change after being set which is not unreasonable for configuration options.

However if you are dealing with variables that can mutate in a concurrent environment, you might have a race condition if you pass by pointer reference rather than by value

Re: Reusable and type-safe options for Go APIs

#30
post #15

Earlier quoted context omitted.

Publicly released in an unstable state November 2009, meaning (almost) 8 years ago. Which is right where I personally draw the line for "almost a decade". It's been your preferred language from the moment it was available as a preview, three years before it hit 1.0?

That's right. I've been using Go since pre 1.0 times. I'm however not by and large a programmer by profession.

> I'm however not by and large a programmer by profession.

That explains it. Different risk tolerances when you're not reliant on it for a living.

Apologies for whatever I was flagged for, if I came across as rude in text that was not my intention. Legitimate curiosity.

Post reply on HN