Type of app effects in Spork

Background

I’m using Spork for a simple web app that relies on a GraphQl server. In the client side, I’m using graphqlclient. The question isn’t really about these libraries but it helps with contextualising the issue.

Side effects in Spork are run within a natural transformation:

data AppEffect a
  = PersistInLocalStorage (Tuple String String) a
  | GraphQlQuery Token SelectionSet a

runAppEffect :: AppEffect ~> Aff
runAppEffect = case _ of
   -- implementation

The type variable a can be the type for actions/messages as in this example.

The Issue

Having a be a simple type for actions is limited. So, instead I used a function type: a -> b; where a is the result from some side effect and b is the next action. With graphqlQueryRequest being polymorphic, a more realistic runAppEffect looks like:

type ToNextAction a = ∀ b. b -> a
type GraphQlResponse a = ∀ b. Either (GraphQLError b) b -> a
type QuerySelectionSet = ∀ b. SelectionSet Scope__RootQuery b

data AppEffect a
  = PersistInLocalStorage (Tuple String String) (ToNextAction a)
  | GraphQlQuery QuerySelectionSet (GraphQlResponse a)

The issue here is with QuerySelectionSet where the compiler complains about b being a rigid type versus another concrete type that is used elsewhere in the update function. In the update function, the selection set has a type:

SelectionSet Scope__RootQuery (Array EntityJson)

I understand the issue but I’m not entirely sure about the best course of action.

Apologies if the explanation is inadequate. I’m also afraid I have to tag @natefaubion :grin: because he’s the maintainer of Spork

Note that Spork only requires that your effect type is a Functor, and can be lowered into Effect callbacks. A natural transformation to Aff is just one common way of doing it, but it’s not a requirement. You can write effects directly with Aff if you want to avoid the indirection of an additional effect functor. The effect functor lets you do dependency injection.

For your issue specifically, there’s multiple things going on. I don’t quite follow what you mean by “b is the next action”, since “action” is a bit of an overloaded term in this context. If you mean you want to sequence multiple effects, then you’ll want your effect type to be Monad. I would focus more on making that work for you than trying to model it with data types like this.

These might be helpful:

https://thomashoneyman.com/guides/real-world-halogen/

The b type argument should be existential not universal (exists instead of forall)

https://thimoteus.github.io/posts/2018-09-21-existential-types.html

Thank you @natefaubion @srghma not just for your replies but also for the excellent libraries here.

I’ll need some time to digest your advice; I didn’t know Spork only required the effect type be a functor nor did I know about purescript-exists. And that is why I love PureScript, and sisters; there’s always something new to learn :grinning_face_with_smiling_eyes:

2 Likes

Using an existential type partially works; the compiler no longer complains about type mismatches within the interpreter. However, it isn’t possible to make a Functor instance of this new type:

newtype GraphQlQueryF a b = GraphQlQueryF
  { selectionSet :: SelectionSet Scope__RootQuery b
  , responseHandler :: Either (GraphQLError b) b -> a
  }

type GraphQlQuery a = Exists (GraphQlQueryF a)

@natefaubion explained why a Functor instance isn’t possible in Discord thread.

Equipped with more understanding about the interpreter pattern and existentials, I can’t see a way forward to make the two libraries, Spork and graphqlclient work together?

I also inspected graphql-client but it looks like it has a similar interface.

Ignoring the type above and considering the type of graphqlQueryRequest, is it still possible to use the two libraries work together; hopefully without major refactoring?

Wait, why is a Functor instance not possible?

Unless i’m misunderstanding, it’s because GraphQlQueryF's b, which is GraphQlQuery's a, appears in negative position. I guess you could make an Invariant instance.

Is b not the existential variable in your code?

It is. Does that help in making a functor instance?

I’m now trying to follow @natefaubion’s advice and use the decoder outside the interpreter but I’ll need to dig deeper into the graphql library.

It’s an interesting exercise overall :thinking: