ugorji / go

idiomatic codec and rpc lib for msgpack, cbor, json, etc. msgpack.org[Go]
MIT License
1.86k stars 295 forks source link

Codec between struct and array #282

Closed joneshf closed 5 years ago

joneshf commented 5 years ago

I'm having a bit of trouble understanding if my use case is supported. I'm implementing binary decoding/encoding for Dhall.

The thing that I'm confused about is the encoding of non-trivial expressions. The equality operator, for instance, is encoded as a CBOR array with the first two elements being 3 and 2 respectively. E.g., if we wanted to encode the expression:

True == False

it would be:

[3, 2, true, false]

Is there a way to represent this transformation using go-codec?

More concretely, if I had this type:

type BoolEqual struct {
  left bool
  right bool
}

Could I alter it to decode/encode appropriately?

ugorji commented 5 years ago

If your type implements codec.Selfer, that should be sufficient.

See https://godoc.org/github.com/ugorji/go/codec#Selfer

Give it a shot and let me know how it works for you.

joneshf commented 5 years ago

Sorry, I'm having a little trouble making it work. For simplicity sake, let's look at just False and True:

type Bool struct {
    Value bool
}

I've got an interface for expressions:

type Expression interface {
    alphaNormalize() Expression
    betaNormalize() Expression
}

Is the right way to deal with this by adding the Selfer interface to the Expression interface? Like:

type Expression interface {
    alphaNormalize() Expression
    betaNormalize() Expression

    codec.Selfer
}

I've tried this, and I attempt to use it like:

var expression Expression
codec.NewDecoderBytes(in, handle).MustDecode(&expression)

I end up with an error like:

panic: interface conversion: dhall.Expression is not codec.Selfer: missing method CodecDecodeSelf

What did I mess up?

ugorji commented 5 years ago

This feels like the most complicated example to use, to illustrate something simple. I'm not even sure how to process the example. So let me just try to write out what the solution should look like.

It's more like this:

type BoolEqual struct {
  left bool
  right bool
}

func (s *BoolEqual) CodecEncodeSelf(e *Encoder) { 
  x := []interface{}{3, 2, true, false}
  e.MustEncode(x)
}

func (s *testSelferRecur) CodecDecodeSelf(d *Decoder) { 
  var x = []interface{}{3, 2, false, false}
  d.MustDecode(&x)
  s.left, s.right = x[2].(bool), x[3].(bool)
}

Something like this. i.e. your type should implement the codec.Selfer interface, and during encoding and decoding, if that type is passed, we will delegate to the implementation you define, instead of using our own heuristics to decode it.

joneshf commented 5 years ago

The example is non-trivial because the language is non-trivial. Expressions in Dhall are more than just boolean equality and I'm trying to extrapolate what to do from the simplified example. I can follow the suggestion when there's a single node in the AST:

type BoolEqual struct {
  left bool
  right bool
}

So the suggestion works for the simplified example, and I thank you for that. However even with just boolean equality, the AST needs to also be non-trivial (as you can say True == False == True in Dhall, and that cannot be represented with BoolEqual above). That's the part where I'm stuck, going from the simplified example to the real world.

This is the code I'm working with:

type Expression interface {
  alphaNormalize() Expression
}

type BoolValue struct {
  value bool
}

func (b *BoolValue) alphaNormalize() Expression { return b }

type BoolEqual struct {
  left Expression
  right Expression
}

func (b *BoolEqual) alphaNormalize() Expression { return b }

Is there a way to extend this version of the code to use your Selfer suggestion? If not, is there another way to use this package to make a codec that can? It's fine if this package cannot support this sort of thing.

joneshf commented 5 years ago

your type should implement the codec.Selfer interface, and during encoding and decoding, if that type is passed, we will delegate to the implementation you define, instead of using our own heuristics to decode it.

Sorry, this is the issue. There is no one single type to pass at the start of decoding. We get some bytes in over the wire and only once we start decoding can we know what type it's supposed to be. Does this package support this behavior automatically? Or, do you have to manually write that code? I'm fine with either answer, but I don't know what the answer is.

ugorji commented 5 years ago

You need to use a io.Reader that allows you to peek at some bytes, so you can peek, and then determine what to decode into.

joneshf commented 5 years ago

That's effectively what I'm doing now. Looks like this package doesn't support what I was hoping for. Thanks for the suggestions!