如何在PureScript中使用SimpleJSON解析行多态记录?

如何在PureScript中使用SimpleJSON解析行多态记录?,json,purescript,row-polymorphism,Json,Purescript,Row Polymorphism,我编写了一个实用程序类型和函数,用于帮助解析某些行多态类型(在我的例子中,特别是扩展BaseIdRows: type IdTypePairF r = (identifier :: Foreign, identifierType :: Foreign | r) readIdTypePair :: forall r. Record (IdTypePairF r) -> F Identifier readIdTypePair idPairF = do id <- readNEStr

我编写了一个实用程序类型和函数,用于帮助解析某些行多态类型(在我的例子中,特别是扩展
BaseIdRows

type IdTypePairF r = (identifier :: Foreign, identifierType :: Foreign | r)


readIdTypePair :: forall r. Record (IdTypePairF r) -> F Identifier
readIdTypePair idPairF = do
  id <- readNEStringImpl idPairF.identifier
  idType <- readNEStringImpl idPairF.identifierType
  pure $ {identifier: id, identifierType: idType}
我有一个可以证明我的问题的例子

但是,对于后代来说,这是一个完整的例子:

module Main where

import Control.Monad.Except (except, runExcept)
import Data.Array.NonEmpty (NonEmptyArray, fromArray)
import Data.Either (Either(..))
import Data.HeytingAlgebra ((&&), (||))
import Data.Lazy (Lazy, force)
import Data.Maybe (Maybe(..))
import Data.Semigroup ((<>))
import Data.String.NonEmpty (NonEmptyString, fromString)
import Data.Traversable (traverse)
import Effect (Effect(..))
import Foreign (F, Foreign, isNull, isUndefined)
import Foreign as Foreign
import Prelude (Unit, bind, pure, ($), (>>=), unit)
import Simple.JSON as JSON

main :: Effect Unit
main = pure unit


type ResourceRows = (
  identifiers :: Array Identifier
)
type Resource = Record ResourceRows

type BaseIdRows r = (
  identifier :: NonEmptyString
, identifierType :: NonEmptyString
| r
)
type Identifier = Record (BaseIdRows())

-- Utility type for parsing
type IdTypePairF r = (identifier :: Foreign, identifierType :: Foreign | r)



readNEStringImpl :: Foreign -> F NonEmptyString
readNEStringImpl f = do
  str :: String <- JSON.readImpl f
  except $ case fromString str of
    Just nes -> Right nes
    Nothing -> Left $ pure $ Foreign.ForeignError
      "Nonempty string expected."

readIdTypePair :: forall r. Record (IdTypePairF r) -> F Identifier
readIdTypePair idPairF = do
  id <- readNEStringImpl idPairF.identifier
  idType <- readNEStringImpl idPairF.identifierType
  pure $ {identifier: id, identifierType: idType}

readRecordJSON :: String -> Either Foreign.MultipleErrors Resource
readRecordJSON jsStr = runExcept do
  recBase <- JSON.readJSON' jsStr
  --foo :: String <- recBase.identifiers -- Just comment to check inferred type
  idents :: Array Identifier <- traverse readIdTypePair recBase.identifiers
  pure $ recBase { identifiers = idents }
modulemain其中
import Control.Monad.Except(Except,runExcept)
导入Data.Array.NonEmpty(NonEmptyArray,fromArray)
导入数据。或者(或者(…)
导入Data.HeytingAlgebra(&&),(| |)
导入数据。惰性(惰性,强制)
导入数据。可能(可能(…)
导入数据。半群(())
导入Data.String.NonEmpty(NonEmptyString,fromString)
导入数据。可遍历(遍历)
进口影响(影响(…)
导入外部(F、外部、isNull、isUndefined)
进口外国商品
导入前奏曲(单位,绑定,纯,($),(>>=),单位)
将Simple.JSON导入为JSON
主要影响单位
主=纯单位
类型ResourceRows=(
标识符::数组标识符
)
类型资源=记录资源行
类型BaseIdRows r=(
标识符::非空字符串
,identifierType::非空字符串
|r
)
类型标识符=记录(BaseIdRows())
--用于解析的实用程序类型
类型IdTypePairF r=(标识符::外部,标识符类型::外部| r)
readNEStringImpl::Foreign->F NonEmptyString
readNEStringImpl f=do
str::字符串右键
Nothing->Left$pure$Foreign.ForeignError
“应为非空字符串。”
readIdTypePair::所有r.记录(IdTypePairF r)->F标识符
readIdTypePair IDPAIR=do

id您的问题是,
recBase
不一定属于
Resource
类型

编译器有两个确定
recBase
类型的参考点:(1)事实上
recBase.identifiers
readIdTypePair
一起使用;(2)返回类型
readRecordJSON

从第一点开始,编译器可以得出以下结论:

recBase :: { identifiers :: Array (Record (IdTypePair r)) | p }
recBase :: { identifiers :: a }
对于一些未知的
r
p
。事实是它(至少)名为
identifiers
的字段来自点语法,该字段的类型来自
readIdTypePair
的参数,再加上
idents
数组的事实。但是除了
identifiers
(由
p
表示)之外,还可能有更多的字段,并且
标识符
的每个元素都是部分记录(由
r
表示)

从第二点出发,编译器可以得出以下结论:

recBase :: { identifiers :: Array (Record (IdTypePair r)) | p }
recBase :: { identifiers :: a }
等等,什么?为什么
a
而不是
Array Identifier
?资源
的定义没有明确指定
标识符::Array Identifier

是的,确实如此,但这里有个诀窍:
recBase
的类型不必是
Resource
readRecordJSON
的返回类型是
Resource
,但是在
recBase
readRecordJSON
的返回类型之间有一个记录更新操作
recBase{identifiers=identifiers}
,它可以更改字段的类型

是的,PureScript中的记录更新是plymorphic的。请查看以下内容:

> x = { a: 42 }
> y = x { a = "foo" }
> y
{ a: "foo" }
查看
x.a
的类型是如何更改的?此处
x:{a::Int}
,但
y:{a::String}

代码中也是如此:
recBase.identifiers::Array(IdTypePairF r)
用于一些未知的
r
,但是
(recBase{identifiers=idents})。identifiers::Array Identifier

满足
readRecordJSON
的返回类型,但行
r
仍然未知


要修复此问题,您有两个选项。选项1-制作
readIdTypePair
完整记录,而不是部分记录:

readIdTypePair :: Record (IdTypePairF ()) -> F Identifier
选项2-明确指定
recBase
的类型:

recBase :: { identifiers :: Array (Record (IdTypePairF ())) } <- JSON.readJSON' jsStr

如果你这样做是为了美观,我没有异议。但是如果你不知道-现在你知道了:-)

你的问题是
recBase
不一定属于
资源类型

编译器有两个确定
recBase
类型的参考点:(1)事实上
recBase.identifiers
readIdTypePair
一起使用;(2)返回类型
readRecordJSON

从第一点开始,编译器可以得出以下结论:

recBase :: { identifiers :: Array (Record (IdTypePair r)) | p }
recBase :: { identifiers :: a }
对于一些未知的
r
p
。它(至少)有一个名为
identifiers
的字段这一事实来自点语法,该字段的类型来自
readIdTypePair
的参数以及
idents
数组的事实。但是除了
标识符
(由
p
表示)之外,可能还有更多的字段,
标识符
的每个元素都是一个部分记录(由
r
表示)

从第二点出发,编译器可以得出以下结论:

recBase :: { identifiers :: Array (Record (IdTypePair r)) | p }
recBase :: { identifiers :: a }
等等,什么?为什么
a
而不是
数组标识符
Resource
的定义是否明确规定了
identifiers::Array Identifier

是的,确实如此,但这里有个诀窍:
recBase
的类型不必是
Resource
readRecordJSON
的返回类型是
Resource
,但是在
recBase
readRecordJSON
的返回类型之间有一个记录更新操作
recBase{identifiers=idents}
,它可以更改字段的类型

是的,PureScript中的记录更新是plymorphic。看看这个:

> x = { a: 42 }
> y = x { a = "foo" }
> y
{ a: "foo" }
查看
x.a
的类型如何更改?这里是
x::{a::Int}
,但是
y:{a::String}

代码中也是如此:
recBase.identifiers::Array(IdTypePairF r)
用于一些未知的
r
,但是
(recBase{identifiers=idents})。identifiers::Array Identifier