mus-format / mus-go

A set of serialization primitives for Golang
MIT License
77 stars 3 forks source link

Why the changes in the interfaces ? #10

Open sylr opened 2 months ago

sylr commented 2 months ago

Hi 👋

I don't understand the change to generic names for the mus.Unmarshaller/mus.Marshaller/mus.Sizes functions.

Could you please explain the motivation?

Regards.

ymz-ncnk commented 2 months ago

Hi!

I just opened an issue #11 that describes a possible future of mus-go. I wonder what you think about it? Also there is a post on reddit.

In general it was done to write code like this:

func MarshalSliceProtobuf[T any](sl []T, m mus.Marshaller[T], bs []byte) (n int) {
    for i := 0; i < len(sl); i++ {
        n += m.Marshal(sl[i], bs[n:]) // instead of m.MarshalMUS(...)
    }
    return
}
sylr commented 2 months ago

I think Marshal/Unmarshal are too generic terms to be employed by any library in a language like Go that heavily relies on interfaces. encoding/json uses MarshalJSON, gopkg.in/yaml.v3 uses MarshalYAML ... etc.

MarshalMUS/UnmarshalMUS made very much sense.

ymz-ncnk commented 2 months ago

Sorry for the delay in response.

We can still write:

  func MarshalFooMUS(foo Foo, bs []byte) (n int) {
    ...
  }

And use it as, for example:

  ord.MarshalSlice[Foo](sl, nil, mus.MarshallerFn[Foo](MarshalFooMUS), bs)

I don't understand the change to generic names for the mus.Unmarshaller/mus.Marshaller/mus.Sizes functions.

I mean, mus-go is already a more general thing and can be used to implement different formats, not only MUS.

sylr commented 2 months ago

I don't use functions, all my MUS usage is structured around methods that implements the former interfaces. Having to use Marshal/Unmarshal for only for MUS is not ideal, but, having to have to reserve Size for MUS is not something I want.

// MessageMUS is a MUS marshaller for Message.
//
//nolint:all
type MessageMUS struct{}

var messageMUS = MessageMUS{}
var _ mus.Marshaller[Message] = (*MessageMUS)(nil)
var _ mus.Unmarshaller[Message] = (*MessageMUS)(nil)
var _ mus.Sizer[Message] = (*MessageMUS)(nil)

// MarshalMUS marshals struct mus encoded bytes.
func (MessageMUS) MarshalMUS(m Message, bs []byte) (n int) {
    ...
    return n
}

// UnmarshalMUS unmarshals mus encoded bytes.
func (MessageMUS) UnmarshalMUS(bs []byte) (m2 Message, n int, err error) {
    ...
    return m2, n, nil
}

// SizeMUS computes the length of MUS encoded bytes for the struct.
func (MessageMUS) SizeMUS(m Message) (n int) {
    ...
    return n
}

Then I have my generic Mus Marshaller function for NATS.

// Marshaller is the interface that wraps the MusMarshaller and MusSizer methods.
type Marshaller[T any] interface {
    mus.Marshaller[T]
    mus.Sizer[T]
}

// MarshalMUS encodes a message to a NATS message.
func MarshalMUS[T Marshaller[T]](inputMsg, outputMsg *nats.Msg, v *T) error {
    bs := make([]byte, (*v).SizeMUS(*v))
    (*v).MarshalMUS(*v, bs)
        ...
    return nil
}
ymz-ncnk commented 2 months ago

Sorry for the inconvenience. As a temporary solution, I can suggest to define the Marshaller interface as:

type Marshaller[T any] interface {
    MarshalMUS(t T, bs []byte) (n int)
    SizeMUS(t T) (size int)
}

, think this should work.

ymz-ncnk commented 2 months ago

As a temporary solution, I can suggest to define the Marshaller interface as:

Actually, this is not a very good idea. It is better to use something like namespaces:

type Foo struct {
    Int int
}

// First of all, let's define a general Serializer struct. It is simply an 
// union of the Marshal, Unmarshal, and Size functions.
type Serializer[T any] struct {
    Marshal   mus.MarshallerFn[T]
    Unmarshal mus.UnmarshallerFn[T]
    Size      mus.SizerFn[T]
}

// And a Foo serializer.
var FooSerializerMUS = Serializer[Foo]{
    Marshal: func(foo Foo, bs []byte) (n int) {
        return varint.MarshalInt(foo.Int, bs)
    },
    Unmarshal: func(bs []byte) (foo Foo, n int, err error) {
        foo.Int, n, err = varint.UnmarshalInt(bs)
        return
    },
    Size: func(foo Foo) (size int) {
        return varint.SizeInt(foo.Int)
    },
}

// Then a SerializableMUS interface, for structures that support serialization
// in the MUS format.
type SerializableMUS[T any] interface {
    MUS() Serializer[T]
}

// And finally, add MUS support to Foo.
func (f Foo) MUS() Serializer[Foo] {
    return FooSerializerMUS
}

// Now generic MarshalMUS function may look like this
func MarshalMUS[T SerializableMUS[T]](t T) (bs []byte){
    var (
        s = t.MUS()
        bs = make([]byte, s.Size(t))
    )
    s.Marshal(t, bs)
    return
}

// And could be used as - MarshalMUS(foo).

@sylr