Skip to content

Instantly share code, notes, and snippets.

@sgf-dma
Last active December 18, 2024 18:39
Show Gist options
  • Select an option

  • Save sgf-dma/f97f73f209bb98a07db6e170d2e18854 to your computer and use it in GitHub Desktop.

Select an option

Save sgf-dma/f97f73f209bb98a07db6e170d2e18854 to your computer and use it in GitHub Desktop.
package main
import (
"fmt"
"encoding/json"
"reflect"
)
// Unmarshal json to a struct containing interface field. See full
// corresponding Haskell code in separate file.
// Haskell:
// class I a where
// f :: a -> ()
type I interface {
f()
}
type S struct {
X int
}
var _ I = S{}
func (s S) f() {}
// Haskell: data R1 = forall a. I a => R1 a Int
// I.e. data constructor type is 'R1 :: forall a. I a => a -> Int -> R1',
// and the type is existential.
type R1 struct {
I I
Y int
}
// Haskell: data R2 a = R2 a Int
// I.e. data constructor type is 'R2 :: forall a. a -> Int -> R2 a',
// and this is regular (not existential) type.
type R2[T any] struct {
I T
Y int
}
// Version of 'S' with pointer receiver for use with 'R3'.
type SP struct {
X int
}
var _ I = (*SP)(nil)
func (s *SP) f() {}
// Haskell does not have reflect, so there is no equivalent of 'R3'.
type R3 struct {
I I
Y int
}
func (r *R3) UnmarshalJSON(bs []byte) (err error) {
v := struct {
I json.RawMessage
Y int
}{}
err = json.Unmarshal(bs, &v)
if err != nil {
return
}
r.Y = v.Y
if r.I != nil {
p := reflect.ValueOf(r.I)
if p.Kind() != reflect.Pointer {
err = fmt.Errorf("R3.UnmarshalJSON(): Interface 'r.I' uses value receiver '%v'", p.Type())
return
}
if err = json.Unmarshal(v.I, p.Interface()); err != nil {
return
}
}
return
}
// Haskell:
// class Read a => I2 a where
// f2 :: a -> ()
type I2 interface {
f2()
json.Unmarshaler
}
type S2 struct {
X int
}
// Dummy instance, but it's required by 'I2'.
var _ json.Unmarshaler = (*S2)(nil)
func (s *S2) UnmarshalJSON(bs []byte) error {
v := struct {X int}{}
if err := json.Unmarshal(bs, &v); err != nil {
return err
}
s.X = v.X
return nil
}
// Implementing I2 with value receiver has no sense, because
// 'json.Unmarshaler' can only work with pointer receiver.
var _ I2 = (*S2)(nil)
func (s *S2) f2() {}
// Haskell: data R4 = forall a. I2 a => R4 a Int
// I.e. data constructor is: 'R4 :: forall a. I2 a => a -> Int -> R4'
type R4 struct {
I I2
Y int
}
// json.UnmarshalJSON() method in Go has a pointer to actual data, so it
// actually does know what type is behind existential interface 'I2'. But
// Haskell only uses types and does not look at values during type check.
// Therefore, a function equivalent to 'json.UnmarshalJSON()' should have a
// type witness, which tell Haskell, which type to use for interface 'I2'. So,
// the implementation of 'json.Unmarshaler will look like:
//
// class Unmarshaler b a where
// read' :: Proxy a -> String -> b
//
// and an instance for R4 will be:
//
// instance I2 a => Unmarshaler R4 a where
func main() {
jsBytes := []byte(`{"I": {"X": 1}, "Y": 2}`)
var err error
r1 := R1{}
err = json.Unmarshal(jsBytes, &r1)
fmt.Printf("R1: err = %v, r1 = %v\n", err, r1)
r2 := R2[S]{}
err = json.Unmarshal(jsBytes, &r2)
fmt.Printf("R2: err = %v, r2 = %v\n", err, r2)
r3 := R3{I: S{}}
err = json.Unmarshal(jsBytes, &r3)
fmt.Printf("R3{S}: err = %v, r3 = %v\n", err, r3)
r3p := R3{I: &SP{}}
err = json.Unmarshal(jsBytes, &r3p)
fmt.Printf("R3{SP}: err = %v, r3p = %v, r3p.I = %v\n", err, r3p, r3p.I)
r4 := R4{I: &S2{}}
err = json.Unmarshal(jsBytes, &r4)
fmt.Printf("R4: err = %v, r4 = %v, r4.I = %v\n", err, r4, r4.I)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment