Why does `Ask` contain `Applicative` and not `Functor`
Ask only uses #applicative.map (and notably not #applicative.pure), so shouldn't it only require a Functor?
Functor won't cut it, because the "ask adds no effects" law needs Apply.
Not many types have Apply and not Applicative. Map[K, *] is one. This might work (uncompiled, untested):
new Ask[Map[K, *], K] {
val apply = Apply[Map[K, *]]
def ask = Map.empty.withDefault(identity)
}
I'm struggling to think of other examples, but maybe that inspires someone else.
I feel quite uncomfortable having constraints that are just for laws/testing, in general. it opens the door to adding Apply constraints to any type that purports to be pure, which seems unreasonable to me. should LiftValue[From, To] (see #625) also require Apply[To] so that its laws can assert the same purity as Ask wrt #liftF?
personally, I'd feel much more comfortable just having the additional constraint on the laws, and allow writing Ask instances whose lawfulness is not completely testable. (this of course leads to the question of what to do if some but not all of the laws have the necessary constraints to run...)
edit:
Not many types have
Applyand notApplicative
FlatMap is just as valid a subtype of Apply as Applicative, so regardless it should at minimum be reduced to Apply probaby. which is a pain of a migration
another example of something that seemingly ought to require/contain Apply by this logic: Kleisli. how do we know the F returned by the function is pure otherwise?
def ask = Map.empty.withDefault(identity)
A simpler definition that compiles is based on an empty map:
implicit def askForMap[K, E]: Ask[Map[K, *], E] =
new Ask[Map[K, *], E] {
val applicative: Apply[Map[K, *]] = Apply[Map[K, *]]
def ask[E2 >: E]: Map[K, E2] = Map.empty
}
But this fails "ask adds no effects":
Expected: Map(Ṃ -> 俅㷕)
Received: Map()
And I was going to say that withDefault has all sorts of weird properties. Like that the default values are not .getable.
scala> val m = Map.empty[String, String].withDefault(identity); Some(m("x")); m.get("x")
val m: Map[String, String] = Map()
val res9: Some[String] = Some(x)
val res10: Option[String] = None
That causes my originally proposed instance to fail the same law for the same reason. So we've backslid from looking for a useful example to even a lawful example.
I feel quite uncomfortable having constraints that are just for laws/testing, in general
cats-mtl uses a different typeclass encoding (called the scato encoding), preferring composition over subtyping. But an Ask instance extends and interoperates with the functionality of Apply. Non-test code should be able to constrain on Ask[F, E] and call things that require only Apply[F].
another example of something that seemingly ought to require/contain Apply by this logic: Kleisli. how do we know the F returned by the function is pure otherwise?
Kleisli is just a data type, and not subject to laws. Its instances are subject to the typeclasses' laws, and the Apply[Kleisli[F, A, *]] requires an Apply[F].
Also, there is no general Apply law that fa *> fb <-> fb.
scala> val fa = None; val fb = Some("x"); fa *> fb
val fa: None.type = None
val fb: Some[String] = Some(x)
val res13: Option[String] = None
The law in question is only saying that ask.ask *> fa <-> fa. We're not constraining what arbitrary F[A] values might do. We're constraining what the F[E] returned by ask.ask can do. This law assures that Ask[IOLocal, A].ask is based on .get and not .modify.