5

I am wondering if there is a typeclass in Cats or Scalaz which offers an operator like this:

def scan[G[_],A,B](zero: B)(g: G[A],f: (A,B) => B):G[B]

Or if there exists some mathematical definition for such operator (something like Monad for bind/flatMap).

The idea of this typeclass would be apply a binary function to a type constructor and obtain back the same type constructor but with a different type parameter (the same type that binary function returns).

I think would be similar to scanLeft of Scala Standard Library collections.

salc2
  • 577
  • 5
  • 14
  • Leaves me wondering what the properties of the returned value are – what are the laws of this typeclass? I mean I know it for `scan` on collection-like types. But can we generalize this? – ziggystar Jan 04 '18 at 19:05
  • @ziggystar Yes, for sure we could generalize it. The returned value (`G[_]`) could be a Monad/Functor like.. the `scan` itself can be implemented in terms of `flatMap/map` with an state in the method maybe. My thinking is something similar to Elm's `foldp`. Sorry for the late response. – salc2 Jan 11 '18 at 16:02

2 Answers2

7

One of possible implementation is to traverse with a State:

import cats._, data._, implicits._

def scan[G[_]: Traverse: Applicative: MonoidK, A, B](list: G[A], zero: B, f: (B, A) => B): G[B] = {
  def generate(a: A): State[B, B] =
    for {
      prev <- State.get[B]
      next =  f(prev, a)
      _    <- State.set(next)
    } yield next

  zero.pure[G] <+> list.traverse(generate).runA(zero).value
}

This works like scanLeft from stdlib for Vectors and Lists (but not options!), but requires quite a lot of typeclasses! Unfortunately, stdlib scanLeft prepends the initial element, so the result collection will always be one element larger than the original one and no single typeclass provides any similar operation.

If you are fine with not prepending zero, all you need on G[_] is Traverse and this is not half-bad. If you're not, you're probably better off generalizing using subtypes

Oleg Pyzhcov
  • 7,323
  • 1
  • 18
  • 30
2

Answering the original question, no, I don't think there is such a typeclass already. However, you can implement similar functionality with Foldable.

Using Cats:

import cats.data.NonEmptyList
import cats.Foldable

implicit class ScanLeftable[F[_], T](val ts: F[T]) extends AnyVal {
  def scanLeft2[U](zero: U)(f: (U, T) => U)
                  (implicit fo: Foldable[F]): NonEmptyList[U] = {
    Foldable[F].foldLeft(ts, NonEmptyList.of(zero)) { (lu, t) =>
      f(lu.head, t) :: lu
    }.reverse
  }
}

import cats.instances.list._
val z = List(5, 10).scanLeft2(0)((a, b) => a + b)
println(z == NonEmptyList.of(0, 5, 15)) //true

You might try to make it more generic in terms of the return type, or return a lazy structure like Iterator, though. However, I'm not sure how generic it could be without introducing a new typeclass.

EDIT: the method is scanLeft2 strictly so that I can be sure the standard library one isn't called.

Jakub Kozłowski
  • 493
  • 1
  • 6
  • 13
  • 1
    This looks pretty much like the way to go, but it would be interesting to see an implementation that did return a `F[U]` not a `NonEmptyList[U]`. Might need to include something like the `CanBuildFrom` from the standard library. – Joe K Dec 20 '17 at 19:01
  • As @JoeK says I want it returns `F[U]` instead of a concrete collection like `NonEmptyList` where `F[U]` could be a custom infinite collection like for example. – salc2 Dec 21 '17 at 10:37