Skip to content

Provide a free Functor or Applicative wrapper to enable pretend computations over Symbolic values #494

@endgame

Description

@endgame

In #459, there have been a number of approaches proposed to solve the "make a Command return multiple values" problem. @ocharles used a GADT to build a defunctionalised expression tree with the specific functions he needed, and I proposed using Coyoneda (free Functor) or even a free Applicative to "pretend" to compute over Symbolic values. When running an Ensure hook, an actual value can be extracted from the state Concrete using lowerCoyoneda or retractAp or whatever.

@ChickenProp then discovered that these existing free wrappers are not suitable for hedgehog for two main reasons:

  1. They do not have the functor parameter as the final types argument, which means you have to manually write out instances for any barbies typeclasses, instead of using its generic-based deriving; and
  2. These free wrappers lack a usable Show instance, which hedgehog requires.

He proposed a custom FVar type, which I think has the following advantages and drawbacks:

  • (pro) Has FunctorB and TraversableB instances, and probably any others from barbies which we need;
  • (con?) Provides instance (Eq a, Eq1 v) => Eq (FVar a v), but instance Eq1 Symbolic ignores its function argument, so this means that given x :: Symbolic a, FVar (const 0) x == FVar (const 1) x will return True. This seems more like a "Symbolic's fault", but I think we should not provide the Eq instance if we can get away without it;
  • (pro) Provides instance Show (FVar a v), so it's actually usable with hedgehog;
  • (con) Not a Functor, because the type arguments are the wrong way around; and
  • (con) Not possible to apply a function over multiple Symbolic values.

I think the ergonomic benefits justify building our own free wrapper. I also think we should consider going beyond a free Functor to an analogue of a free Applicative, either the one in free:Control.Applicative.Free.Fast (which I don't really understand) or the one in free:Control.Applicative.Free.Final. It seems like it'd be pretty useful to be able to lift pure functions across multiple Symbolic values, and if #493 is accepted then we'll have instance Applicative Concrete to make the retraction from a free Applicative.

On the API side I have some suggestions. Here's what I think we should provide:

-- I think not working with `Var a v` will be more ergonomic, since if we get a good
-- way of returning multiple values from a `Command` in #459, we'll have various
-- `v a` values kicking around.
fvar :: Show a => v a -> FVar a v
fapply :: Show a => (a -> b) -> v a -> FVar b v
($$) = fapply -- infix alias
mapFVar :: (a -> b) -> FVar a v -> FVar b v
(<$$>) = mapFVar -- infix alias, same fixity as (<$>)
fconcrete :: FVar a Concrete -> a
fretract :: Functor/Applicative v => FVar a v -> v a -- Exact constraint depends on whether we build a free `Functor` or `Applicative`.

-- If you agree that we should go to a free `Appliactive`
apFVar :: FVar (a -> b) v -> FVar a v -> FVar b v
(<@@>) = apFVar -- infix alias, same fixity as (<*>). Note that (<**>) exists in `base` and so is unavailable. There's precedent for `<@>` being a weird variant of `<*>` in some FRP libraries.
liftFVar2 :: (a -> b -> c) -> FVar a v -> FVar b v -> FVar c v
liftFVar3 :: (a -> b -> c -> d) -> FVar a v -> FVar b v -> FVar c v -> FVar d v

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions