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



我编写了一个实用程序类型和函数,旨在帮助解析某些行多态类型(在我的情况下,特别是任何扩展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}

然而,当我尝试使用它时,它会导致代码出现这种类型的错误(在我较大的代码库中,在我实现readIdTypePair函数之前,一切都很好(:

No type class instance was found for
Prim.RowList.RowToList ( identifier :: Foreign
, identifierType :: Foreign
| t3
)
t4
The instance head contains unknown type variables. Consider adding a type annotation.
while applying a function readJSON'
of type ReadForeign t2 => String -> ExceptT (NonEmptyList ForeignError) Identity t2
to argument jsStr
while checking that expression readJSON' jsStr
has type t0 t1
in value declaration readRecordJSON
where t0 is an unknown type
t1 is an unknown type
t2 is an unknown type
t3 is an unknown type
t4 is an unknown type

我有一个生动的要点来说明我的问题。

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

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 }

您的问题是recBase不一定是Resource类型。

编译器有两个参考点来确定recBase的类型:(1(recBase.identifiersreadIdTypePair一起使用的事实和(2(readRecordJSON的返回类型。

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

recBase :: { identifiers :: Array (Record (IdTypePair r)) | p }

对于一些未知的CCD_ 9和CCD_。它(至少(有一个名为identifiers的字段,这一事实来自点语法,该字段的类型来自readIdTypePair的参数以及identsArray的事实。但除了identifiers(用p表示(之外,可能还有更多的字段,并且identifiers的每个元素都是部分记录(用r表示(。

从第二点来看,编译器可以得出结论:

recBase :: { identifiers :: a }

等等,什么?为什么a而不是Array IdentifierResource的定义没有明确规定identifiers :: Array Identifier吗?

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

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

> 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

另外,我觉得有必要评论一下你指定记录的奇怪方式:你首先声明一行,然后用它做一个记录。仅供参考,它可以直接用大括号完成,例如:

type Resource = {
identifiers :: Array Identifier
}

如果你这样做是出于审美原因,我没有反对意见。但如果你不知道-现在你知道了:-(

最新更新