Hacker News
points by thayne 3 days ago | 0 comments
First of all, the json2 Marshall API is json specific.

Secondly, other languages like rust, haskell, scala, etc. Have json APIs that are typesafe and give you compile time errors if you try to use a type that doesn't support (de) serialization . In the case of rust at least, the API is also general and can be used for other data types as well.

Even in go, you could have some more type safety if Marshall took an interface (such as MarshallerTo) rather than `any`. Although then you have the problem of how you implement that interface. So I guess you probably want some kind of "derive" functionality, or macros that can approximate it for a convenient and type-safe API.

P.S. gophers like to talk about how explicit being better than implicit, but using reflection for marshalling is very implicit.

[–] hnlmorg 3 days ago
Except it’s not JSON specific. The article literally talks about how their function design was chosen for similarity with the v1 API. And I’d wager that the go team are planning on reusing that function signature for other encoders too. Or at least expecting it to be reused by the wider ecosystem.

If you want type safety then pass a struct to the marshal. Which is actually the idiomatic way to do it anyway. The type safety would be in that.

I’m not going to disagree with you that Gos type system is a rather blunt tool nor that Gos expressiveness is extremely rigid compared to many other popular languages. All of that is completely true. But I don’t agree that the v2 JSON API is a good example of that problem.

[–] thayne 2 days ago
> Except it’s not JSON specific.

The function is in the `encoding/json/v2` package. It might have a similar design, and maybe they will use the same function signature for other encoders, but it the functions are very much json specific.

> If you want type safety then pass a struct to the marshal.

But if you use a struct that has a field that can't be marshalled, you don't get a compile error, you get a runtime SemanticError. That's the problem.

[–] hnlmorg 2 days ago
> The function is in the `encoding/json/v2` package. It might have a similar design, and maybe they will use the same function signature for other encoders, but it the functions are very much json specific.

But the point you keep missing is that the function signature needs to be encoder agnostic for it to work as an interface. That’s a hard requirement.

And since interfaces are a convenience to allow common abstractions to be reused at runtime, you cannot depend on things like generics (ie compile time abstractions) to generalise the data types for disparate serialisation formats.

I know this limitation well because I’ve built a shell with a typed pipeline and support for different serialisation formats. So make heavy use interfaces to achieve this. And I’ve spent a lot of time investigating alternative approaches. Each time, I’ve found interfaces to be a massive advantage despite any tradeoffs I end up making do to generalising a “class” (to borrow a term from OOP).

I’d honestly love to have a more sophisticated type system in Go. But that’s not a limitation in this specific instance.

> But if you use a struct that has a field that can't be marshalled, you don't get a compile error, you get a runtime SemanticError. That's the problem.

My point here was: if you know you’re creating a struct to pass to the mashaller at compile time, then you’re already going to use JSONAble fields. So it’s not an issue.

And if you don’t know what you’re passing to the mashaller a compile time, then you’re dependent on runtime checks regardless of the expressiveness of Gos type system.

I don’t disagree that Gos type system is inflexible. But in respect to this specific use case, your argument is a storm in a teacup. The type system isn’t a limiting factor here.

[–] thayne 2 days ago
> But the point you keep missing is that the function signature needs to be encoder agnostic for it to work as an interface.

It's a function, not a method. So it can't be part of an interface.

I suppose you could make an argument for passing it as a function argument to a higher ordered function. But that is kind of an unusual way to use such a function. And isn't a common pattern in go.

> My point here was: if you know you’re creating a struct to pass to the mashaller at compile time, then you’re already going to use JSONAble fields. So it’s not an issue.

Maybe. Maybe not. For a simple struct where all the fields are primitive types sure. But if you have a complicated hierarchy, you might miss that some strict in the hierarchy has a field that can't be marshaled.

Say you are marshaling a struct that contains a struct from a third party library, and it works fine. Then you update to a new version of that library, and that added a new field for a type that doesn't work, say a map[A]string where A is a struct. Your program still compiles fine, but then you get an error at runtime when you try to serialize something that worked before. Hopefully you have tests that catch it.

[–] hnlmorg 2 days ago
> It's a function, not a method. So it can't be part of an interface.

The mashaller here is a function, yes. But having a consistent signature means you can interchange those marshallers. It’s a trick I use heavily in murex to enable typed pipelines.

And I do this via a pipe interface. So it’s not uncommon to reuse function signatures for interface types (fewer runtime abstractions) while retaining some level of dynamism.

> Say you are marshaling a struct that contains a struct from a third party library, and it works fine. Then you update to a new version of that library, and that added a new field for a type that doesn't work, say a map[A]string where A is a struct.

The first mistake there is to blindly forward an internal API struct that you don’t control, and depend on that business logic.

Which comes back to my point about if you want type safety then you’d create a struct to be marshelled rather than passing data you don’t control.

I do take your point that the compiler in smarter languages would catch this. But you’d equally be told not to do this sort of thing on those languages too. So it feels like a moot point.