new(unmarshal-to-interface): Add variants of unmarshaling json to interface struct... master
authorsgf <sgf.dma@gmail.com>
Wed, 18 Dec 2024 18:47:08 +0000 (21:47 +0300)
committersgf <sgf.dma@gmail.com>
Wed, 18 Dec 2024 18:47:08 +0000 (21:47 +0300)
unmarshal-to-interface/unmarshal-to-interface.go [new file with mode: 0644]
unmarshal-to-interface/unmarshal-to-interface.hs [new file with mode: 0644]

diff --git a/unmarshal-to-interface/unmarshal-to-interface.go b/unmarshal-to-interface/unmarshal-to-interface.go
new file mode 100644 (file)
index 0000000..fe8ea5d
--- /dev/null
@@ -0,0 +1,157 @@
+
+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)
+}
diff --git a/unmarshal-to-interface/unmarshal-to-interface.hs b/unmarshal-to-interface/unmarshal-to-interface.hs
new file mode 100644 (file)
index 0000000..5466099
--- /dev/null
@@ -0,0 +1,121 @@
+{-# LANGUAGE ViewPatterns #-}
+
+import Data.List
+
+-- Here is Haskell equivalent of unmarshaling json to interface in Go. See
+-- corresponding Go file for full Go code.
+
+-- I'll use regular 'Show' and 'Read' instances instead of marshaling to/from
+-- json. 'Show' instance will be required during creation of values of 'Rx'
+-- types. But it's actually does not matter, where to require it or require or
+-- not at all.
+
+-- Go:
+--  type I interface {
+--      f()
+--  }
+class I a where
+    f :: a -> ()
+
+-- Go:
+--  type S struct {
+--      X int
+--  }
+data S = S Int
+  deriving (Show, Read)
+
+-- Go:
+--  var _ I = S{}
+--  func (s S) f() {}
+instance I S where
+    f _ = ()
+
+-- Go:
+--  type R1 struct {
+--      I I
+--      Y int
+--  }
+data R1 = forall a. (I a, Show a) => R1 a Int
+deriving instance Show R1
+-- So data constructor type is: R1 :: forall a. (I a, Show a) => a -> Int -> R1 .
+-- I.e. i can't say what 'a' was used by looking at type 'R1' of some 'x :: R1'.
+-- And therefore 'Read' instance won't typecheck with:
+--
+--      Ambiguous type variable ‘a0’ arising from a use of
+--      ‘GHC.Read.readPrec’ prevents the constraint ‘(Read a0)’ from being
+--      solved.
+--
+--deriving instance Read R1
+
+-- Go:
+--  type R2[T any] struct {
+--      I T
+--      Y int
+--  }
+data R2 a = R2 a Int
+  deriving (Show, Read)
+
+-- Go:
+--  type I2 interface {
+--      f2()
+--      json.Unmarshaler
+--  }
+class Read a => I2 a where
+    f2 :: a -> ()
+
+data S2 = S2 Int
+  deriving (Show, Read)
+
+instance I2 S2 where
+    f2 _ = ()
+
+-- Go:
+--  type R4 struct {
+--      I I2
+--      Y int
+--  }
+data R4 = forall a. (I2 a, Show a) => R4 a Int
+deriving instance Show R4
+-- So data constructor type is: R4 :: forall a. (I2 a, Show a) => a -> Int -> R4 ,
+-- which means, that by looking at value type R4 i can't say what type 'a' to
+-- use. Therefore, 'Read' instance can't be written
+--
+--    Ambiguous type variable ‘a0’ arising from a use of ‘R4’
+--    prevents the constraint ‘(I2 a0)’ from being solved.
+--
+--deriving instance Read R4
+
+-- But Go's 'json.Unmarshaler' interface works differently: it lookups
+-- instance not just by type 'R4', but it also already has (pointer to) value
+-- of type 'R4'. And by looking at value it may tell, what type 'a' was used
+-- during its creation. Thus, (because i don't have pointers here) i need a
+-- witness of type 'a' to construct value of type 'R4'.
+data Proxy a = Proxy
+
+-- Go: 'json.Unmarshaler' interface. 'Proxy' serves the role of pointer
+-- receiver for determining into which interface to unmarshal struct fields,
+-- if any.
+class Unmarshaler b a where
+    read' :: Proxy a -> String -> b
+
+-- Read string produced by 'Show' instance: "R4 (S2 1) 2"
+instance (I2 a, Show a) => Unmarshaler R4 a where
+    read' _ (stripPrefix "R4" -> Just i2str) =
+        let [(i2, rest)] = readParen True (reads @a) $ i2str
+        in  R4 i2 (read rest)
+
+-- And here it is (note explicit type applications):
+--
+--  ghci> read' @R4 (Proxy @S2) $ show (R4 (S2 1) 2)
+--  R4 (S2 1) 2
+
+main :: IO ()
+main = do
+    let r2 :: R2 S
+        r2 = read $ show (R2 (S 1) 2)
+    print r2
+
+    let r4 :: R4
+        r4 = read' (Proxy @S2) $ show (R4 (S2 1) 2)
+    print r4
+