For an uncertainty-propagating Approximate
type, I'd like to have instances for Functor
through Monad
. This however doesn't work because I need a vector space structure on the contained types, so it must actually be restricted versions of the classes. As there still doesn't seem to be a standard library for those (or is there? please point me. There's rmonad, but it uses *
rather than Constraint
as the context kind, which seems just outdated to me), I wrote my own version for the time being.
It all works easy for Functor
class CFunctor f where
type CFunctorCtxt f a :: Constraint
cfmap :: (CFunctorCtxt f a, CFunctorCtxt f b) => (a -> b) -> f a -> f b
instance CFunctor Approximate where
type CFunctorCtxt Approximate a = FScalarBasisSpace a
f `cfmap` Approximate v us = Approximate v' us'
where v' = f v
us' = ...
but a direct translation of Applicative
, like
class CFunctor f => CApplicative' f where
type CApplicative'Ctxt f a :: Constraint
cpure' :: (CApplicative'Ctxt f a) => a -> f a
(#<*>#) :: ( CApplicative'Ctxt f a
, CApplicative'Ctxt f (a->b)
, CApplicative'Ctxt f b) => f(a->b) -> f a -> f b
is not possible because functions a->b
do not have the necessary vector space structure* FScalarBasisSpace
.
What does work, however, is to change the definition of the restricted applicative class:
class CFunctor f => CApplicative f where
type CApplicativeCtxt f a :: Constraint
cpure :: CAppFunctorCtxt f a => a -> f a
cliftA2 :: ( CAppFunctorCtxt f a
, CAppFunctorCtxt f b
, CAppFunctorCtxt f c ) => (a->b->c) -> f a -> f b -> f c
and then defining <*>#
rather than cliftA2
as a free function
(<*>#) = cliftA2 ($)
instead of a method. Without the constraint, that's completely equivalent (in fact, many Applicative
instances go this way anyway), but in this case it's actually better: (<*>#)
still has the constraint on a->b
which Approximate
can't fulfill, but that doesn't hurt the applicative instance, and I can still do useful stuff like
ghci> cliftA2 (\x y -> (x+y)/x^2) (3±0.2) (5±0.3) :: Approximate Double
0.8888888888888888 +/- 0.10301238090045711
I reckon the situation would essentially the same for many other uses of CApplicative
, for instance the Set
example that's already given in the original blog post on constraint kinds.
So my question:
is <*>
more fundamental than liftA2
?
Again, in the unconstrained case they're equivalent anyway. I actually have found liftA2
easier to understand, but in Haskell it's probably just more natural to think about passing "containers of functions" rather than containers of objects and some "global" operation to combine them. And <*>
directly induces all the liftAμ
for μ ∊ ℕ, not just liftA2
; doing that from liftA2
only doesn't really work.
But then, these constrained classes seem to make quite a point for liftA2
. In particular, it allows CApplicative
instances for all CMonad
s, which does not work when <*>#
is the base method. And I think we all agree that Applicative
should always be more general than Monad
.
What would the category theorists say to all of this? And is there a way to get the general liftAμ
without a->b
needing to fulfill the associated constraint?
*Linear functions of that type actually do have the vector space structure, but I definitely can't restrict myself to those.
FScalarBasisSpace
restriction to theApproximate
constructor, you don't need any context for<*>
, because you can get the type class constraints by pattern matching on the input values. – RingerFScalarBasisSpace
as a constraint on all kinds of uncertainties. I sure need it to implement the obvious instances... I'll think about it. – SyncopeApproximate(a->b)
would become impossible to start with. I would need a separate constructor for this case, which seems quite ugly to me right now, though perhaps it's not that bad, if I properly hide it from the module interface. – Syncope