Open endgame opened 1 year ago
(I don't have strong feelings about a free applicative here. I suppose the main benefit would be applying a function to two symbolic values and storing the result in state? I don't think I've had a use for this so far, but that's just me.)
On FVar
: I agree that the Eq
instance is weird. I think it would be reasonable to get rid of it, and keep Var
around for when people want Eq
and Ord
. (Or maybe it's more accurate to say I think the instance Eq1 Symbolic
is weird? I'm not sure. The law is liftEq (==) = (==)
and it does satisfy that. For a while I thought we might expect liftEq (\_ _ -> False) x x == False
, which the instance violates. But that doesn't hold for Maybe
or []
so it's clearly not a sensible thing to expect. So this instance does seem fine. And the Eq FVar
instance seems fine to me too. But somehow the combination seems weird.)
I suppose the main benefit would be applying a function to two symbolic values and storing the result in state?
Exactly. I can't imagine an exact use right now (I was sketching with a system of Users, Roles, and User<->Role attachments, but that seems doable with the current command type). Extending to a free Applicative
seems like it solves situations where you want to apply a pure function to 2..N Symbolic
values, and store that Symbolic
result somewhere that shouldn't see all the Symbolic
arguments (like the input of a Command
). These seem plausible and not hard to build support for (as opposed to, say, going all the way to a free Monad
), which is why I think it's worthwhile.
instance (...) => Eq (FVar a v)
is weird.
I think there's an additional coherence that we expect due to the relation between Concrete
and Symbolic
that makes this instance weird. (It's even worse for Ord
where the concrete values could compare very differently to the generated names.)
I vote that we scrap those Eq
/Ord
instances but keep instance Eq1/Ord1 Symbolic
and instance Eq/Ord (Symbolic a)
, which seem really important for maintaining e.g. sets of things generated from previous commands.
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 usingCoyoneda
(freeFunctor
) or even a freeApplicative
to "pretend" to compute overSymbolic
values. When running anEnsure
hook, an actual value can be extracted from thestate Concrete
usinglowerCoyoneda
orretractAp
or whatever.@ChickenProp then discovered that these existing free wrappers are not suitable for
hedgehog
for two main reasons:barbies
typeclasses, instead of using its generic-based deriving; andShow
instance, which hedgehog requires.He proposed a custom
FVar
type, which I think has the following advantages and drawbacks:FunctorB
andTraversableB
instances, and probably any others frombarbies
which we need;instance (Eq a, Eq1 v) => Eq (FVar a v)
, butinstance Eq1 Symbolic
ignores its function argument, so this means that givenx :: Symbolic a
,FVar (const 0) x == FVar (const 1) x
will returnTrue
. This seems more like a "Symbolic
's fault", but I think we should not provide theEq
instance if we can get away without it;instance Show (FVar a v)
, so it's actually usable withhedgehog
;Functor
, because the type arguments are the wrong way around; andSymbolic
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 freeApplicative
, either the one infree:Control.Applicative.Free.Fast
(which I don't really understand) or the one infree:Control.Applicative.Free.Final
. It seems like it'd be pretty useful to be able to lift pure functions across multipleSymbolic
values, and if #493 is accepted then we'll haveinstance Applicative Concrete
to make the retraction from a freeApplicative
.On the API side I have some suggestions. Here's what I think we should provide: