Also thanks a lot for the reference to `assemble` and `Finite`. This is something that might come in handy in my work on selective functors (https://blogs.ncl.ac.uk/andreymokhov/selective).

]]>When I read this I wondered if you could derive one from the other using the algebraic structure of ADTs (“algebra of algebraic data types”), to go from one to the other but of course that doesn’t work:

(1 + (fv ^ (fv ^ k))) ^ k

((1 + fv) ^ k) ^ (fv ^ k)

And I’m sure the existentially quantified term needs to be treated differently, but no idea how. In any case I would be really interested if there was some way to reason algebraically either to determine that there must exist some isomorphism to Task2, or to derive the implementation.

In poking at this I did notice a couple things that might not really be that interesting…

First, as you probably knew `toTask` doesn’t require any constraint, and this definition is a little more terse:

toTask :: Task2 Unconstrained k v -> Task Unconstrained k v

toTask = flip . fmap sequenceA

For a second I was excited thinking that we could have:

fromTask :: Task Unconstrained k v -> Task2 Unconstrained k v

fromTask = fmap sequenceA . flip

But that of course is impossible, since we can’t have Traversable ((->) a)

On a whim though a hayoo search turned up http://hackage.haskell.org/package/countable-1.0/docs/Data-Searchable.html#v:assemble

So I guess an alternative would have `(f v)` and `k` both be constrained to `Finite` but that’s probably not useful at all.

]]>Under some hypothetical world with [scrap your typeclasses](http://www.haskellforall.com/2012/05/scrap-your-type-classes.html) we could have multiple instances of a class for the same datatype without even the need for newtype wrappers like `EitherT`. In such circumstances you’d again unambiguously see the “ derived from the monad to be identical to the monad’s `ap`, and the “ from Validation’s standard applicative instance to be some other thing.

In theory I don’t think is not a problem; after all we don’t expect the empty for `IntProduct` to obey the laws with respect to `IntSum`’s `mappend`. In practice, this means your encoding is much more idiomatic haskell.

]]>instance {-# OVERLAPS #-} Monad (EitherT b (Validation e))

My feeling is that the line below breaks the `ap` law

EitherT (Failure e) >>= _ = EitherT (Failure e)

since the behaviour is different from the underlying Applicative instance.

]]>This stuff gets a little awkward to express in Haskell because instance dictionaries aren’t explicit and overlap is a nono, but I think the `Validation` stuff is also a monad instance for `EitherT (Validation e) b`s. I’ve put some experimentation along these lines in a gist here:

https://gist.github.com/masaeedu/ca3cdd13fd83422601ac0cc32dd7714d

]]>handle :: Applicative f => f (Either a b) -> f (a -> b) -> f b

handle f fa = fmap (bimap f id) fa

This doesn’t type check. Feel free to link to a GitHub gist where we could continue the discussion with code highlighting.

I think one can make it work in two ways:

* By eventually rewriting your code into `handleA`, which is in the blog post. But then when you have got a `Right b`, you don’t need the handler effect and would like to skip it, but the Applicative interface doesn’t allow you to do that.

* You could also use `join` to collapse `f (f b)` into `f b`. But that requires a monad, hence, being equivalent to `handleM`.

Hope this clarifies things!

]]>“`

handle :: Applicative f => f (Either a b) -> f (a -> b) -> f b

handle f fa = bimap f id fa

“`

Look: no new typeclasses! The `Selective` doesn’t even narrow anything, as any `Applicative` can do it.

It just uses standart `Bifunctor (Either e a)` instance. ]]>

There are currently three ways of modelling callback failure using our Task abstraction:

(1) Include failures into the type of values v, for example:

data FileContents = FileNotFound | Contents ByteString

This is convenient when the task has a built-in notion of failures. For example, a build rule may be designed to cope with some missing files, e.g. if fetch “username.txt” returns FileNotFound, a build rule could use the literal string “User” as a default value. In this case it will depend on the fact that the file “username.txt” is missing, and will be rebuilt if the user later creates this file.

In many cases the result is isomorphic to choosing v = Maybe v’.

(2) Include failures into the context f, for example:

values :: Map String Integer

fetch :: String -> Maybe Integer

fetch k = Map.lookup k values

Now we are choosing f = Maybe.

This is convenient when the task itself has no built-in notion of failures, e.g. we can model Excel formulas as pure arithmetic functions, and introduce failures “for free” if/when needed by instantiating Task with an appropriate f.

(3) Finally, the task itself might not want to encode failures into the type of values v, but instead demand that f has a built-in mechanism for expressing failures. This can be done by choosing an appropriate c, e.g ApplicativeZero, MonadZero, Alternative, MonadPlus, etc.

Now, let’s look at the type you propose:

type Task c k v = forall f. c f => (k -> Maybe (f v)) -> k -> Maybe (f v)

Here the first Maybe is outside f, so a failure is known ‘statically’ by simply looking at the given key (similar to the above fetch example with Map.lookup). This will not work in cases when you need to perform some effects in order to determine if the lookup has failed, e.g. to check if the file is actually missing on disk. Here you want something like IO (Maybe v) instead, and I see no way of fitting this into the above type signature.

Your second type:

type Task c k v = forall f. c f => (k -> f v) -> k -> f v

is essentially the same as ours with respect to failures, but it doesn’t have a mechanism to (statically) indicate that a given key is an input. Are you suggesting to indicate this using the failure mechanism? While this works for some examples, I don’t think it’s sufficient in general: some of our build systems exploit the fact that it is possible to determine whether a key is an input statically, without performing an actual computation, i.e. they require the function isInput implemented above. After removing Maybe from the result, it becomes harder or impossible to implement isInput.

I think I should add the above explanation of three ways of modelling failure into the blog post.

]]>type Task c k v = forall f. c f => (k -> Maybe (f v)) -> k -> Maybe (f v)

But you could also require that f has failure built in. Then Task Applicative becomes Task Alternative (with empty instead of Nothing) and Task Monad becomes Task MonadPlus (with mzero instead of Nothing) and Task becomes a proper optic:

type Task c k v = forall f. c f => (k -> f v) -> k -> f v

If your original f doesn’t support failure you can replace it with Compose Maybe f.

]]>