3 ms·
I don't think it has anything to do with protocol buffers, but the behavior derives from the same intrinsic motivation. If you don't have a zero value, a progr
by jrockway 6y ago
I don't think it has anything to do with protocol buffers, but the behavior derives from the same intrinsic motivation.
If you don't have a zero value, a programmer has to pick one. What are they going to pick? Probably what the language picks for you, "int n = 0;", 'string foo = "";', etc. For a language, it doesn't really matter which side you pick (force programmers to select a value, or auto-assign one). For network protocols, defining empty is an important optimization -- if the client and the server are guaranteed to agree, you don't have to send the value. This is especially important where the client and the server aren't released at exactly the same time; the server may have a new field in the Request type that the client doesn't fill in. With a predefined zero value, it doesn't matter. (You can always add fields to your message to get the same effect, if you actually care. I've never seen anyone do this in any API, including ones that use serialization that doesn't have the concept of zero values. It's why Javascript has the ?. operator!)
Finally, Go came out in the proto2 era, which did have the concept of set and unset fields (and let the proto file declare arbitrary default values). Honestly, I wrote a ton of Go involving protos at Google, and never saw proto3 until after I left Google.
- pyrale 6y ago> If you don't have a zero value, a programmer has to pick one. Most languages without null have an optional type which is used exactly for that. In these languages, this means the None value exists when you have optional things, but that, then, the compiler forces you to check whether your value is set when you want to use it. Serialization libraries then get the choice to handle these optional values as they wish, which can be not to send a field. It's one of those things that may be hard to think about from an external pov, but it works just fine. From users of these languages, proto2 was okayish, and proto3 was a massive regression. Another thing that's missing in protobuf is the ability to define union types. That's one frequently asked feature from typed functional languages for serialization protocols.
- jrockway 6y agoFor programming languages, I agree with you. Though I don't really see how "int*" is different from "Optional<int>". You can write: func foo(maybeInt *int) { if maybeInt == nil { panic("not so optional!!!!") } ... } Just as easily as: func foo(maybeInt Optional<int>) { switch maybeInt { case None: panic("not so optional!!!!!") ... } } To me, it just isn't a big deal. Your program is going to crash and return unexpected results when it expects something to exist and it doesn't, and the type system won't save you. (Even Haskell crashes at runtime when a pattern match doesn't resolve. Don't see how that's any different than a nil pointer dereference. Your live demo is ruined.) For protocols,an Optional type just pushes the problem one level down. Is the optional value "None" because the client didn't know the field existed, or because they explicitly set it to "None"? You can't tell. I think rather than going 3 levels deep, it's easier to just define the default values and not distinguish between these three cases. If you want an Optional value, you can make yours as complex as you wish: message Optional { int value = 1; bool empty_because_the_user_said_so = 2; bool client_has_version_of_proto_with_this_field = 3; } Now if you get (0, false, false), you know that's because the client is outdated. If you get (0, false, true), you know that's because the user didn't feel like sending a value. And if you get (0, true, true), you know the user wanted 0. (Of course, there are all the other cases that you have to handle -- what about (1, false, true), or (1, true, false)?) I think you'll find that nobody but programming language purists want this feature. If your message is: message FooRequest { int foos_to_return = 1; } You do the right thing regardless of whether the 0 in foos_to_return is what the user wanted, something the user forgot to set, or the user has an old version of FooRequest ("message FooRequest{}").*
- travv0 6y agofunc foo(maybeInt Optional<int>) { switch maybeInt { case None: panic("not so optional!!!!!") ... } } This function would never take an Optional in real life if it's just going to crash on None so I'm not sure what you're getting at here. The benefit of using Optional/Maybe is that you're encoding at the type level whether it makes sense for a given variable to be able to be nothing, and if it does make sense, the compiler makes sure you check whether it's nothing or not, but this is an example of where that doesn't make sense so the type should just be int instead of Optional<int>.
- hedora 6y ago> For a language, it doesn't really matter which side you pick (force programmers to select a value, or auto-assign one). Even (modern) C/C++ handles this aspect of memory safety better: There is no default value, and reading from an uninitialized value is a compile time error (usually, because C/C++ have baggage).