0
votes

I'm having trouble with a Functor instance for a type which is basically just nested Either and Maybe.

data Tuple a b = Tuple a b
data Primitive = String String | Boolean Boolean | Number Number | Null
data JsonValue = Object (Map String JsonValue) | Array (List JsonValue) | Primitive
type Path = List String
data JsonGraphValue = JsonGraphObject (Map String JsonGraphValue) | Atom JsonValue | Ref Path | Error JsonValue | JsonPrimitive Primitive

newtype JsonGraphRecResult a = JsonGraphRecResult (Either String (Tuple (Maybe a) (List Path)))
instance jsonGraphRecResultFunctor :: Functor JsonGraphRecResult where
  map f (JsonGraphRecResult (Right (Tuple (Just value) paths))) = JsonGraphRecResult (Right (Tuple (Just (f value)) paths))
  map f value = value

I get the following error pointing to the "value" word at the end of the code above.

  Could not match type

    a1

  with type

    b0


while trying to match type JsonGraphRecResult a1
  with type JsonGraphRecResult b0
while checking that expression value
  has type JsonGraphRecResult b0
in value declaration jsonGraphRecResultFunctor

where b0 is a rigid type variable
      a1 is a rigid type variable

It's not clear to me why JsonGraphRecResult is any different from the following Blah type which compiles fine:

newtype Blah a = Blah (Maybe a)
instance blahFunctor :: Functor Blah where
  map f (Blah (Just x)) = Blah (Just (f x))
  map f value = value

The following gist can be pasted directly into the "Try PureScript" online REPL in order to replicate the error.

2

2 Answers

0
votes

Figured out the problem. I can't simply return the input value for the map function in the event that Either is Left, because the input value is not the right type. Here's a simplified version of the problem.

-- This is wrong because value is a Functor a, whereas map must return Functor b
map value@Nothing f = value

-- This is right, because even though both sides are Nothing, the right-hand side is a Maybe b vs. Maybe a
map Nothing f = Nothing
0
votes

You should be able to just derive Functor instance for your type. But before you derive an instance for your final type you should derive it for your Tuple type (or just use Tuple from Data.Tuple ;-)):

data Tuple a b = Tuple a b
derive instance functorTuple :: Functor (Tuple a)

As you can see Functor instance can be defined only for types of kind * -> * so in this case we can map over type which occupies "last position" in Tuple.

In order to derive an instance for your JsonGraphRecResult you have to change order of types in it's internal Tuple to fulfill "last position" requirement:

newtype JsonGraphRecResult a =
  JsonGraphRecResult (Either String (Tuple (List Path) (Maybe a)))
derive instance functorJsonGraphRecResult :: Functor JsonGraphRecResult

Here is relevant interactive snippet on trypurescript.org so you can play with this implementation.

There are more type classes for which you can use this deriving mechanism: Ord, Eq, Generic, Newtype...

It is also worth to point out in this context that in Purescript you have additional deriving option which is "newtype deriving". It's syntax is a bit different because it contains newtype keyword after derive - for example:

derive newtype instance someClassMyType :: SomeClass MyType

Newtype deriving is used for newtype "wrappers" for deriving instances of classes which given newtype internal type has already defined instances. In other words when you have newtype T = T a you can derive newtype instances for T for every class which a has instance of.

There is also another strategy - you can also use generic implementations of methods defined for some type classes in Data.Generic.Rep. These implementations can be used for types which are instances of Generic class... but this is whole another story ;-)

You can find more information about deriving in Phil's "24 Days of Purescript" series:

https://github.com/paf31/24-days-of-purescript-2016