-
Notifications
You must be signed in to change notification settings - Fork 111
Description
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:
- They do not have the functor parameter as the final types argument, which means you have to manually write out instances for any
barbiestypeclasses, instead of using its generic-based deriving; and - These free wrappers lack a usable
Showinstance, which hedgehog requires.
He proposed a custom FVar type, which I think has the following advantages and drawbacks:
- (pro) Has
FunctorBandTraversableBinstances, and probably any others frombarbieswhich we need; - (con?) Provides
instance (Eq a, Eq1 v) => Eq (FVar a v), butinstance Eq1 Symbolicignores its function argument, so this means that givenx :: Symbolic a,FVar (const 0) x == FVar (const 1) xwill returnTrue. This seems more like a "Symbolic's fault", but I think we should not provide theEqinstance if we can get away without it; - (pro) Provides
instance Show (FVar a v), so it's actually usable withhedgehog; - (con) Not a
Functor, because the type arguments are the wrong way around; and - (con) Not possible to apply a function over multiple
Symbolicvalues.
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