0

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

1 回答 1

2

您的问题是recBase不一定是 type Resource

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

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

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

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

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

recBase :: { identifiers :: a }

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

嗯,是的,确实如此,但诀窍是:类型recBase不一定是Resource. 的返回类型readRecordJSONResource,但在recBase和返回类型之间readRecordJSON代表一个记录更新操作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)对于一些 unknown 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
}

如果您出于审美原因这样做,我不反对。但如果你不知道 - 现在你知道了:-)

于 2020-11-19T02:49:08.270 回答