Search code examples
haskellprotocol-buffersprotobuf-3

Using protbuf3, how can I express the type 'Map string (Maybe CustomType)'?


I am trying to share a large dictionary/map between a client and a service. I need to be able to bidirectionally set values, and delete values from the dictionary/map without passing the entire map back and forth each time.

I know I can create a map using:

map<string, CustomType> cells = 3;

(Docs for protobuf3 maps)

I see a sugestion to use oneof here: https://github.com/google/protobuf/issues/1606

However, the syntax suggested in that bug report doesn't make sense to me:

message Test1 {
 oneof a_oneof {
  int32 a = 1;
 }
}

How would I test for that? How would I store None?

I would try to create something like:

message None{}
message MaybeCustomType{
 oneof maybe{
   CustomType just = 1;
   None       none = 2;
 }
}

But I'm not at all convinced that this is the elegant and correct solution.

I also considered changing my schema entirely and doing.

map<string, CustomType> cells = 1;
repeated string deleted_cells = 2;

However I don't like that solution because it creates an undefined case. What happens if a cell named "foo" appears in both the "cells" map and the "deleted_cells" map? Furthermore, it abstracts the data away from the computation. I'd like to be able to pass a cell to a function which modifies and potentially decides to delete the cell, and so it seems natural to me that information about cell deletion be stored near the cell itself.


Solution

  • Your language tag mentions haskell but I'm not sure which library implementation you're using to provide proto3 support, so I'll try to provide a generic answer.

    Overview

    The thread you pointed to suggests using oneof with a single field since it's possible to represent a null (really the absence of the field). Fortunately you don't need to create a None type for this purpose since it is built into the language-specific generated source files. Take a look at the Language Guide for oneof...

    You can check which value in a oneof is set (if any) using a special case() or WhichOneof() method, depending on your chosen language.

    The "if any" part is the magic you're looking for. The generated C++ code for a oneof definition will provide a special oneof_name_case() method that will tell you if one of the fields have been set...

    OneofNameCase oneof_name_case() const: Returns the enum indicating which field is set. Returns ONEOF_NAME_NOT_SET if none of them is set.

    A similar method is generated for Java code: getOneofNameCase() returns ONEOFNAME_NOT_SET if none of the oneof fields are set.

    Example

    Starting from a test message...

    message Test1 {
        oneof single_field_oneof {
            int32 some_int = 1;
        }
    }
    

    If you were using C++ you could use code similar to the following to handle the oneof field...

    int main(int argc, char* argv[]) {
        Test1 test;
    
        // <Populate message somehow>
    
        if (test.single_field_case() == Test1::kSomeInt) {
            std::cout << "Field is set, value is " << test.some_int() << std::endl;
        } else {
            assert(test.single_field_case() == Test1::SINGLE_FIELD_NOT_SET);
            std::cout << "Field is not set" << std::endl;
        }
    }
    

    Anyway, as I mentioned I'm not familiar with the specific proto3 language bindings for Haskell, but the oneof feature should have similar functionality in the library that you're using.