Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sumtype enums #151

Merged
merged 15 commits into from
Nov 1, 2017
Merged

Sumtype enums #151

merged 15 commits into from
Nov 1, 2017

Conversation

inanna-malick
Copy link
Contributor

@inanna-malick inanna-malick commented Oct 26, 2017

#150


This change is Reviewable

@googlebot
Copy link

Thanks for your pull request. It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

📝 Please visit https://cla.developers.google.com/ to sign.

Once you've signed, please reply here (e.g. I signed it!) and we'll verify. Thanks.


  • If you've already signed a CLA, it's possible we don't have your GitHub username or you're using a different email address. Check your existing CLA data and verify that your email is set on your git commits.
  • If your company signed a CLA, they designated a Point of Contact who decides which employees are authorized to participate. You may need to contact the Point of Contact for your company and ask to be added to the group of authorized contributors. If you don't know who your Point of Contact is, direct the project maintainer to go/cla#troubleshoot.
  • In order to pass this check, please resolve this problem and have the pull request author add another comment and the bot will run again.

@inanna-malick
Copy link
Contributor Author

I signed the CLA (by adding myself to the takt OSS google group), cc @judah

@googlebot
Copy link

CLAs look good, thanks!

-- = Prelude.show k
-- showEnum k = Prelude.show k
-- readEnum "Enum2a" = Prelude.Just Enum2a -- alias
-- readEnum k = Text.Read.readMaybe k
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suspect this doesn't do what you think it does, for two reasons:

  • If the value is known, we should use the constructor instead of FooEnum'Unrecognized
  • If the value is not known, I think this reads a value of type FooEnum rather than of type int.

You probably want instead something like readMaybe k >>= maybeToEnum

Also please add tests for the above cases.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

readMaybe k >>= maybeToEnum assumes that the textual representation of these enum's are Ints. I was trying to reproduce the pre-existing logic in which strings like "Enum2a", "Enum2", etc are converted into instances of the sum type. Should this function instead take strings holding ints mapping to different enum values, eg readEnum "1" -> Just Foo'Enum1?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I see, I think I misread your code earlier.

Taking another look, we actually can't use the Show and Read instances for FooEnum to implement showEnum/readEnum. Because for nested enums like

message Bar {
  enum Foo { A = 1; }
}

we want showEnum Bar'A = "A" and readEnum "A" = Bar'A, not showEnum Bar'A = "Bar'A" and readEnum "Bar'A" = Bar'A. It could probably be described better in the docs, but readEnum/showEnum are specifically for the proto text format representation of the enum:
https://github.com/google/proto-lens/blob/master/proto-lens/src/Data/ProtoLens/Message.hs#L231

I think we should just take the string representations (not the ints like I suggested earlier), as in the code for proto2:
https://github.com/google/proto-lens/blob/master/proto-lens-protoc/src/Data/ProtoLens/Compiler/Generate.hs#L374

readEnum "Enum2" = Enum2
readEnum "Enum2a" = Enum2a
etc.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That sounds good, but that still leaves us with no ability to serialize/deserialize unrecognized enum's. I'm assuming readEnum . showEnum == id is a property we want to maintain. If so what should showEnum Foo'FooEnum Foo'Unrecognized... result in?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, good point, we do need something sensible for showEnum at least. How about this?

showEnum Enum2 = "Enum2"
showEnum (Foo'Unrecognized (Foo'UnrecognizedValue n)) = show n

readEnum "Enum2" = Enum2
readEnum "Enum2a" = Enum2
readEnum s = Foo'Unrecognized . UnrecognizedValue <$> readMaybe s

Since in proto text format, it's fine to refer to an enum by its numeric value rather than the case name.

constructorNumbers = map (second (fromIntegral . (^. number))) constructors

succPairs = zip constructorNames $ tail constructorNames
succDecl funName boundName thePairs =
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think succ should be equivalent to toEnum . incr . fromEnum. The implications are kind of subtle though compared to the proto2 semantics listed here: https://github.com/google/proto-lens/blob/master/proto-lens/src/Data/ProtoLens/Message/Enum.hs#L2
If we do make that change, we should note in Data.ProtoLens.Message.Enum that it only applies to proto2.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good. Is not erroring on succ maxEnum and instead returning SomeEnum'UnrecognizedValue... (fromEnum maxEnum + 1) the desired behavior here?

Do you also want me to redefine pred as toEnum . (-1) . fromEnum? Or should pred minEnum still result in an error?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this would break things, eg for FooEnum as defined in proto3.proto:

  enum FooEnum {
    option allow_alias = true;
    Enum1 = 0;
    Enum2 = 3;
    Enum2a = 3;
  }

it seems like we want succ Enum1 == Enum2, not succ Enum1 == Foo'FooEnum'Unrecognized (Foo'FooEnum'UnrecognizedValue 2)

@@ -16,17 +16,18 @@ message Foo {
}
Sub sub = 5;

enum FooEnum {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What was the reason for changing this test? My preference is to add a new test case if we want to support the different situation. (Or, at minimum, add another test that does check naming of nested enums.)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that was due to a transient issue, I've reverted this change.

@@ ("Prelude.shows" @@ "k"))
]
]
-- data FooEnum = Enum1
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Formatting nit in this comment:

-- data FooEnum
--     = Enum1
--     | Enum2
--     | FooEnum'Unrecognized ...


unrecognizedValueType = tyCon $ unQual unrecognizedValueName

constructors :: [(Name, EnumValueDescriptorProto)]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a shame this now duplicates logic from the proto2 code...not sure if there's a good way to re-combine the two. (They were split apart more in #137.)

@@ -359,3 +359,7 @@ modifyModuleName :: (String -> String) -> ModuleName -> ModuleName
modifyModuleName f (Syntax.ModuleName _ unpacked) =
Syntax.ModuleName () $ f unpacked


modifyIdent :: (String -> String) -> Name -> Name
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code structure nit: we try to define names inside the Definitions module and use them in Generate. In this case, adding new fields to EnumInfo with the Names for the unrecognized types:
https://github.com/google/proto-lens/blob/master/proto-lens-protoc/src/Data/ProtoLens/Compiler/Definitions.hs#L157

Copy link
Collaborator

@judah judah left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall looks good; just a few nits remaining.

, [ match "readEnum" [stringPat pn]
$ "Prelude.Just" @@ con (unQual n)
| v <- enumValues info
, let n = enumValueName v
, let pn = T.unpack $ enumValueDescriptor v ^. name
] ++
[match "readEnum" [pVar "k"] $ ("Prelude.>>=" @@ paren ("Text.Read.readMaybe" @@ "k")) @@ ("Data.ProtoLens.maybeToEnum")]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Break this line which has gotten pretty long.

@@ -68,6 +68,7 @@ import Text.PrettyPrint
testMain :: [Test] -> IO ()
testMain = defaultMain

-- todo use this for enum msg on Foo
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove comment

, testGroup "enum"
[ testCase "aliases are exported" $ Foo'Enum2 @=? Foo'Enum2a
, serializeTo "serializeTo enum"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a copy of this test which uses an unrecognized value?

Also: replace l.107 with more explicit generation:

tagged 6 $ VarInt 3

(where 6 is the proto tag of that field, and 3 is the value corresponding to Enum2)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ping on the first line of the above comment (unless I'm misreading the diff).

More specifically, please add this test which checks that unrecognized values round-trip through serialization as expected.

serializeTo "serializeTo enum unknown"
    (def & enum .~ toEnum 5 :: Foo)
    "enum: 5"
    $ tagged 6 $ VarInt 5

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

whoops, sorry about that. Fixed.

@=? map show [Foo'Enum1, Foo'Enum2, Foo'Enum2a, toEnum 5]
["Enum1", "Enum2", "Enum2", "6"]
@=? map showEnum [Foo'Enum1, Foo'Enum2, Foo'Enum2a, toEnum 6]
[Just Foo'Enum1, Just Foo'Enum2, Just Foo'Enum2, Nothing, Nothing]
[Just Foo'Enum1, Just Foo'Enum2, Just Foo'Enum2, maybeToEnum 4, maybeToEnum 5]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test nit (working around the fact that the constructor isn't provided): Add a new test that checks fromEnum <$> maybeToEnum 4 == Just 4

-- (FooEnum'UnrecognizedValue (Prelude.fromIntegral k)))
-- showEnum (FooEnum'Unrecognized (FooEnum'UnrecognizedValue k))
-- = Prelude.show k
-- showEnum k = Prelude.show k
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this comment needs to be updated since you're not calling Prelude.show anymore.

-- deriving (Prelude.Eq, Prelude.Ord, Prelude.Show, Prelude.Read)
, newtypeDecl unrecognizedValueName
"Data.Int.Int32"
$ deriving' ["Prelude.Eq", "Prelude.Ord", "Prelude.Show, Prelude.Read"]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we remove the Read instance, to make sure it's not being used accidentally?

(Also because we'd like toEnum/maybeToEnum to be the only way to construct these values)

Up to you.

Copy link
Collaborator

@judah judah left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks again for taking on this change!

One small request remaining, otherwise LGTM.

, testGroup "enum"
[ testCase "aliases are exported" $ Foo'Enum2 @=? Foo'Enum2a
, serializeTo "serializeTo enum"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ping on the first line of the above comment (unless I'm misreading the diff).

More specifically, please add this test which checks that unrecognized values round-trip through serialization as expected.

serializeTo "serializeTo enum unknown"
    (def & enum .~ toEnum 5 :: Foo)
    "enum: 5"
    $ tagged 6 $ VarInt 5

@judah judah merged commit 7a6f900 into google:master Nov 1, 2017
@inanna-malick inanna-malick deleted the sumtype-enums branch November 1, 2017 03:51
avdv pushed a commit to avdv/proto-lens that referenced this pull request Aug 9, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants