I wrote a utility type and function that is meant to aid in parsing certain row-polymorphic types (sepcifically, in my case, anything that extends 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}
When I try to use it, however, it causes the code to get this type error (in my larger code base, things were working fine before I implemented the readIdTypePair function):
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
I have a live gist that demonstrates my issue.
But, here is the complete example as it stands, for posterity:
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 }
Your problem is that
recBaseis not necessarily of typeResource.The compiler has two points of reference for determining the type of
recBase: (1) the fact thatrecBase.identifiersis used withreadIdTypePairand (2) the return type ofreadRecordJSON.From the first point the compiler can conclude that:
for some unknown
randp. The fact that it has (at least) a field namedidentifierscomes from the dot-syntax, and the type of that field comes fromreadIdTypePair's parameter combined with the fact thatidentsis anArray. But there could be more fields besidesidentifiers(which is represented byp), and every element ofidentifiersis a partial record (which is represented byr).From the second point the compiler can conclude that:
Wait, what? Why
aand notArray Identifier? Doesn't the definition ofResourceclearly specify thatidentifiers :: Array Identifier?Well, yes, it does, but here's the trick: the type of
recBasedoesn't have to beResource. The return type ofreadRecordJSONisResource, but betweenrecBaseand return type ofreadRecordJSONstands a record update operationrecBase { identifiers = idents }, which can change the type of the field.Yes, record updates in PureScript are plymorphic. Check this out:
See how the type of
x.achanged? Herex :: { a :: Int }, buty :: { a :: String }And so it is in your code:
recBase.identifiers :: Array (IdTypePairF r)for some unknownr, but(recBase { identifiers = idents }).identifiers :: Array IdentifierThe return type of
readRecordJSONis satisfied, but the rowris still unknown.To fix, you have two options. Option 1 - make
readIdTypePairtake a full record, not a partial one:Option 2 - specify the type of
recBaseexplicitly:Separately, I feel the need to comment on your weird way of specifying records: you first declare a row and then make a record out of it. FYI it can be done directly with curly braces, for example:
In case you're doing it this way for aesthetic reasons, I have no objections. But in case you didn't know - now you know :-)