1

On stackage.org, the following cyclic declaration exists for liftA2 and <*> for the Applicative typeclass.

       (<*>) = liftA2 id
liftA2 f x y = f <$> x <*> y

Is a non-cyclic declaration available for liftA2 or <*> on the site. Are such completely cyclic references an oversight?

UPDATE:

The following (necessary) clarifying declarations seems to be missing from hoogle docs:

<*> :: Functor F => F (a -> b) -> F a -> F b

and by implication (due to cyclic declaration)

liftA2 :: Functor F => (a -> b -> c) -> F a -> F b -> F c
George
  • 2,451
  • 27
  • 37
  • 4
    The idea is that for each `instance` you implement at least one of the two, and the other is then implemented automatically. – Willem Van Onsem Apr 14 '21 at 20:36
  • There is a chicken and egg problem here. It looks cute but the definition seems silly. `liftA2 f x y = f <$> x <*> y = f <$> x (liftA2 id) y = ...`. Documentation shouldn't be a puzzle, life is too short. – George Apr 14 '21 at 20:53
  • 4
    but a class in Haskell is not like in OO programming. The typeclass does not determine the behavior, the *instance*s do. – Willem Van Onsem Apr 14 '21 at 20:58
  • If the definition of `a` is `not b` and that the definition of `b` is `not a`, are they defined? Well, perhaps that they are distinct but that's it. If recollection serves, the definition of `(<*>) :: Functor F => F (a -> b) -> F a -> F b`, however, it is not stated. The absence of such an assertion makes the existing definition incomplete... I think. – George Apr 14 '21 at 21:48
  • a documentation for Haskell simply does not exist. after two jumps from your link we finally land on a [page](https://www.stackage.org/haddock/lts-17.9/base-4.14.1.0/Control-Applicative.html#v:liftA2) which is useful to your purposes here; it is not a documentation page though. it is an annotated library source. if it was a _documentation_ page, you _would_ be able to click on _any keyword_ there, like e.g. _`class`_, which would bring you to a page describing what "`class`" is, mentioning the "default definitions" among many other things. no such page exists AFAIK. – Will Ness Apr 18 '21 at 21:01
  • for an example of a (very) dry but still a documentation, see eg. http://clhs.lisp.se/Front/index.htm. it is very technical, but it has a glossary, a TOC and several indices. practically everything in that language is discoverable and clickable in that documentation. no such thing exists for Haskell. officially, there is a Report, and there is a "gentle tutorial". valuable learning resources, not a documentation. – Will Ness Apr 18 '21 at 21:04

2 Answers2

6

[…] the following cyclic definition exists for liftA2 and <*> for the Applicative type class.

       (<*>) = liftA2 id
liftA2 f x y = f <$> x <*> y

Is a non-cyclic definition available for liftA2 or <*> on the site.

These aren’t definitions of the methods, exactly; they’re default definitions. A typeclass with one parameter is just a set of types, and an instance definition is the price of admission for membership in the set. The Minimal pragma for Applicative tells you that you must implement one of these two methods, and that information is displayed in Haddock documentation.

The actual definitions of liftA2, <*>, and pure are specific to the instances of Applicative. Generally speaking, if a typeclass contains methods that can be implemented using only the other methods of the typeclass, then that method doesn’t strictly need to be part of the class, since it could be a top-level definition with a constraint.

However, such a method may be included anyway. This may be just for convenience, when it’s easier to define an instance in terms of one function even though it’s not the “most fundamental” operation. It’s also often for performance reasons: redundant methods tend to be included when it’s possible to implement a method more efficiently than the default for a particular type. In this case, for example, liftA2 may be able to traverse two structures together more efficiently than traversing one with <$> and then the other separately with <*>.

GHC also offers DefaultSignatures as a way to add more specific defaults, typically defined in terms of Generic, but this only lets you add typeclass constraints, largely for convenience with deriving.

Are such completely cyclic references an oversight?

Not at all, they’re intentional. Cyclic definitions in default implementations of typeclass methods are quite common. For example, Eq is defined in the Haskell Report something like this:

class Eq a where
  (==) :: a -> a -> Bool
  x == y = not (x /= y)

  (/=) :: a -> a -> Bool
  x /= y = not (x == y)

It is possible to forget to implement one of these, so that they both use the default, and thus represent an infinite loop, however:

  • This generates a warning by default (-Wmissing-methods is enabled by -Wdefault).

  • If a Minimal pragma is not specified, all of the methods in the class are assumed to be required.

So really the only other options in this case are to remove one or the other from the class, or omit providing a default for one of them. If you want to know about how these methods are implemented for Applicative, the thing to look at is the instance implementations for concrete type constructors like [], ZipList, Maybe, StateT, and so on.

Jon Purdy
  • 53,300
  • 8
  • 96
  • 166
  • Your equality example does not declared equality in terms of non-equality or vise versa. In particular it does not do the following: `(==) = not $ (/=)` and `(\=) = not $ (==)`. However, that is not the case with `<*>` and `liftA2`, it seems. – George Apr 14 '21 at 21:59
  • 1
    @George: I’m not sure I understand the difference you want to illustrate. In both typeclasses, the default method implementations call each other immediately. `x == y = not (x /= y)` is precisely equivalent to `(==) = (not .) . (/=)` (note that `not $ (/=)` wouldn’t typecheck), and `(<*>) = liftA2 id` likewise is the same as `x <*> y = liftA2 id x y`. They differ only in style (pointfree vs. eta-expanded), and some incidental details about GHC’s optimisation heuristics that aren’t relevant here. – Jon Purdy Apr 14 '21 at 22:36
  • The issue is that equality (or non-equality) is a function that produces a `Boolean` given two values of the same type. That simple non-bottoming assertion is absent from a cyclic declaration of, `x == y = not (x /= y)`, especially if `not` becomes also cyclically defined somehow. Essentially, at some point it seems you do have to state that, `x == y = (true | false)`. I am just surprise to discover the lack of clarity and wondering if I have missed something in the documentation... perhaps a glossary or dictionary. :) – George Apr 14 '21 at 23:28
  • 2
    @George, you have clearly missed *something*, but it is not entirely clear *what*. Perhaps you don't understand how classes and instances work at all? Perhaps you don't understand method defaults? I don't know. – dfeuer Apr 15 '21 at 02:03
  • @George: You must state that *at least one of* `(==)` & `(/=)` is `True` / `False` when applied to some `T`s in `instance Eq T` for some `T :: Type`; and likewise you must state that at least one of `(<*>)` & `liftA2` gives an `F _` when applied to some `F _`s in `instance Applicative F` for `F :: Type -> Type`. The defaults describe an *equivalence* for convenience’s sake. It’s like how (x+y=z)↔(x=z−y) is true regardless of which [group](https://en.wikipedia.org/wiki/Group_%28mathematics%29) you pick, but a non-cyclic *definition* requires a *particular* group, like the integers or rationals. – Jon Purdy Apr 15 '21 at 21:44
  • @JonPurdy The documentation should describe what such non-cyclic forms look like. However, all I saw was cyclic declarations atleast in the case of `<*>` and `liftA2`. – George Apr 16 '21 at 21:29
3

Is a non-cyclic definition available for liftA2?

The implementations of (<*>) and liftA2 are specific to the instances of the Applicative typeclass. Indeed, for each Applicative instance you need to implement pure, and (<*>) or liftA2, or as specified by the MINIMAL pragma:

class Functor f => Applicative f where
    {-# MINIMAL pure, ((<*>) | liftA2) #-}
    -- …

For example for the Maybe instance of Applicative, this is implemented as:

instance Applicative Maybe where
    pure = Just

    Just f  <*> m       = fmap f m
    Nothing <*> _m      = Nothing

    liftA2 f (Just x) (Just y) = Just (f x y)
    liftA2 _ _ _ = Nothing

    Just _m1 *> m2      = m2
    Nothing  *> _m2     = Nothing

Simply implementing pure and (<*>) would here be sufficient, since then liftA2 is implemented in terms of (<*>). It is however often more efficient to implement the other methods as well.

Willem Van Onsem
  • 443,496
  • 30
  • 428
  • 555
  • Thanks. My interest in understanding whether **Hoogle** would be helpful in aiding my understanding of the library types but it seems it is written for a very small circle of insiders and lacks even the linguistic commonsense of non-cyclic references. :) – George Apr 14 '21 at 21:00
  • 3
    @George, if you look up `Applicative` on Hoogle, and follow it to `Control.Applicative` on `Hackage`, you'll see a huge list of instances in the `base` package. Each of those will have a "source" link you can click on to see the relevant instance declaration. – dfeuer Apr 14 '21 at 21:06
  • Thanks. To use an Haskell term, I thought I would reach `bottom` sooner than two links and a search, even as soon as "on the first page of the definition of the type class". Also, isn't having to refer to the actual implementation implies that the abstraction is leaky. I just searched `hackage.haskell.org` and got a mess of implementations...: http://hackage.haskell.org/packages/search?terms=Applicative. – George Apr 14 '21 at 22:12
  • 1
    @George Yes, everything is terrible in the whole language. I recommend that you stay far, far away from it. – Daniel Wagner Apr 14 '21 at 22:30
  • 1
    I wasn't referring to the language I was referring to the documentation on stackage and hoogle. It makes things more of an head scratcher than it needs to be it seems. – George Apr 14 '21 at 22:59
  • @George, in interesting cases, the documentation you seek is found either with the class or the type. Since `Applicative` is a really fundamental class, information about what its instances do is generally part of the documentation of those instances. In some cases, it's left out, usually because 1. There's only one thing it could be, which is boring (e.g., `Applicative Data.Monoid.Sum)`, 2. There's a documented `Monad` instance, or 3. The meaning of the type effectively communicates it (e.g., a `Parser`). – dfeuer Apr 14 '21 at 23:27
  • 1
    @dfeuer I suppose it can be boring but such ramps are important especially if the target audience is the entire world. Documentation should not become itself code. – George Apr 14 '21 at 23:37
  • @George, that may be, but it's also rough to look at a huge page of documentation and not know which parts are actually worth reading. That said, there really *should* be documentation for `Monad (Either e)` and `Applicative (Either e)`. Perhaps you can write some? – dfeuer Apr 15 '21 at 00:15
  • 1
    @dfeuer I am in the process of compiling a minimal list of typeclasses along with their operators and defining invariant equalities. I expected it would be trivial using `hoogle`. I am disappointed. There also doesn't seem to be an easy way to contribute. `Rust` is starting to look pretty good right now... as ugly as that syntax may be. – George Apr 15 '21 at 00:36
  • @George, I really don't understand what you mean. "Collecting invariant equalities"? There are very few typeclasses you need to learn about to understand 90% of Haskell code. You can contribute to the `base` and `array` packages by filing issues and pull requests on the GHC Gitlab, or even the (mirror) GitHub page for small things. Other packages have other issue trackers. – dfeuer Apr 15 '21 at 00:51
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/231154/discussion-between-george-and-dfeuer). – George Apr 15 '21 at 02:18