The user 'singpolyma' asked on reddit if there was some general structure underlying:
data FailList a e = Done | Next a (FailList a e) | Fail e
A free monad was suggested, but I wondered if this could be modeled more generally via applicative functors. In Abstracting with Applicatives, Bazerman shows us that the sum of two applicative functors is also an applicative functor, with bias to the left/right, provided we have a natural transformation in the direction of the bias. This sounds like it's what we need! Thus, I started my proposal, but then quickly ran into problems. Can anyone see solutions to these problems?:
Firstly, we start with the definition of the sum of two functors. I started here because we want to model sum types - either successes or successes and a failure.
data Sum f g a = InL (f a) | InR (g a)
And the two functors we want to work with are:
data Success a = Success [a]
data Failure e a = Failure e [a]
Success
is straight forward - it's essentially Const [a]
. However, Failure e
I'm not so sure about. It's not an applicative functor, because pure
doesn't have any definition. It is, however, an instance of Apply:
instance Functor Success where
fmap f (Success a) = Success a
instance Functor (Failure e) where
fmap f (Failure e a) = Failure e a
instance Apply (Failure e) where
(Failure e a) <.> (Failure _ b) = Failure e a
instance Apply Success where
(Success a) <.> (Success b) = Success (a <> b)
instance Applicative Success where
pure = const (Success [])
a <*> b = a <.> b
Next, we can define the sum of these functors, with a natural transformation from right to left (so a left bias):
instance (Apply f, Apply g, Applicative g, Natural g f) => Applicative (Sum f g) where
pure x = InR $ pure x
(InL f) <*> (InL x) = InL (f <*> x)
(InR g) <*> (InR y) = InR (g <*> y)
(InL f) <*> (InR x) = InL (f <.> eta x)
(InR g) <*> (InL x) = InL (eta g <.> x)
And the only thing we now have to do is define our natural transformation, and this is where it all comes crumbling down.
instance Natural Success (Failure e) where
eta (Success a) = Failure ???? a
The inability to create a Failure
seems to be the problem. Furthermore, even being hacky and using ⊥ isn't an option, because this will be evaluated, in the case where you have InR (Success ...) <*> InL (Failure ...)
.
I feel like I'm missing something, but I have no idea what it is.
Can this be done?
forall (f :: a -> b). eta . fmap f == fmap f . eta
strongly suggests that the error component must be constant. That makes me want to write anDefault e => Applicative (Failure e)
. – J. AbrahamsonApply
/Applicative
instances are weird. I fixed the heads so that they kind-check, but they generally aren't type-checking either!Success a
isn't really isomorphic toConstant [a]
either, though... at the very least, it needs more type indices! – J. AbrahamsonDefault
seems possible, I just can't see what a sane "default error message" would be. Also, your edits were rejected by other SO editors, though they are valid. I'll apply them myself. – ocharlesFunctor
andApply
ofSuccess
andFailure
don't typecheck. Please update them so that we have a working starting point. – Petr