Skip to content

Commit

Permalink
Changed the Indexable implementation to be derivable from Traverse, t…
Browse files Browse the repository at this point in the history
…his way more types get Indexable behaviors for free.
  • Loading branch information
Jacoby6000 committed Sep 15, 2017
1 parent 0117e5b commit b219956
Show file tree
Hide file tree
Showing 5 changed files with 47 additions and 13 deletions.
31 changes: 24 additions & 7 deletions checklist/src/main/scala/checklist/Indexable.scala
@@ -1,30 +1,47 @@
package checklist

import scala.language.higherKinds

trait Indexable[S[_]] {
def zipWithIndex[A](values: S[A]): S[(A, Int)]
def mapWithIndex[A, B](values: S[A])(f: (A, Int) => B): S[B]
def zipWithIndex[A](values: S[A]): S[(A, Int)] = mapWithIndex(values)((a, i) => (a, i))
}

object Indexable {
object Indexable extends LowPriorityIndexable {
def apply[S[_]](implicit indexable: Indexable[S]): Indexable[S] =
indexable

implicit val listIndexable: Indexable[List] =
new Indexable[List] {
def zipWithIndex[A](values: List[A]): List[(A, Int)] =
def mapWithIndex[A, B](values: List[A])(f: (A, Int) => B) = values.zipWithIndex.map { case (a, i) => f(a, i) }
override def zipWithIndex[A](values: List[A]): List[(A, Int)] =
values.zipWithIndex
}

implicit val vectorIndexable: Indexable[Vector] =
new Indexable[Vector] {
def zipWithIndex[A](values: Vector[A]): Vector[(A, Int)] =
def mapWithIndex[A, B](values: Vector[A])(f: (A, Int) => B) = values.zipWithIndex.map { case (a, i) => f(a, i) }
override def zipWithIndex[A](values: Vector[A]): Vector[(A, Int)] =
values.zipWithIndex
}

implicit val streamIndexable: Indexable[Stream] =
new Indexable[Stream] {
def zipWithIndex[A](values: Stream[A]): Stream[(A, Int)] =
def mapWithIndex[A, B](values: Stream[A])(f: (A, Int) => B) = values.zipWithIndex.map { case (a, i) => f(a, i) }
override def zipWithIndex[A](values: Stream[A]): Stream[(A, Int)] =
values.zipWithIndex
}
}
}

trait LowPriorityIndexable {
import cats.Traverse
// Most of the stuff below is stolen from cats 1.0.0-MF's Traverse. Once this lib is on cats 1.0.0, Indexable can disappear because traverse does all of this.
implicit def indexableFromTraverse[S[_]: Traverse]: Indexable[S] = {
new Indexable[S] {
import cats.data.State
import cats.implicits._
def mapWithIndex[A, B](values: S[A])(f: (A, Int) => B): S[B] =
values.traverse(a => State((s: Int) => (s + 1, f(a, s)))).runA(0).value
}
}

}
16 changes: 16 additions & 0 deletions checklist/src/main/scala/checklist/IndexableSyntax.scala
@@ -0,0 +1,16 @@
package checklist

import cats.implicits._
import cats.{Applicative, Traverse}

object IndexableSyntax extends IndexableSyntax

trait IndexableSyntax {
implicit class IndexableOps[S[_], A](sa: S[A])(implicit indexable: Indexable[S]) {
def zipWithIndex: S[(A, Int)] = indexable.zipWithIndex(sa)
def mapWithIndex[B](f: (A, Int) => B): S[B] = indexable.mapWithIndex(sa)(f)

def traverseWithIndex[F[_]: Applicative, B](f: (A, Int) => F[B])(implicit traverse: Traverse[S]): F[S[B]] =
indexable.mapWithIndex(sa)(f).sequence
}
}
8 changes: 4 additions & 4 deletions checklist/src/main/scala/checklist/Rule.scala
Expand Up @@ -8,6 +8,7 @@ import scala.language.higherKinds
import scala.util.matching.Regex
import Message.errors
import cats.data.NonEmptyList
import checklist.IndexableSyntax._

sealed abstract class Rule[A, B] {
def apply(value: A): Checked[B]
Expand Down Expand Up @@ -63,7 +64,7 @@ sealed abstract class Rule[A, B] {
}
}

def seq[S[_]: Indexable: Traverse]: Rule[S[A], S[B]] =
def seq[S[_]: Traverse]: Rule[S[A], S[B]] =
Rule.sequence(this)

def opt: Rule[Option[A], Option[B]] =
Expand Down Expand Up @@ -347,10 +348,9 @@ trait CollectionRules {
case None => Ior.left(messages)
}

def sequence[S[_] : Indexable : Traverse, A, B](rule: Rule[A, B]): Rule[S[A], S[B]] =
def sequence[S[_] : Traverse : Indexable, A, B](rule: Rule[A, B]): Rule[S[A], S[B]] =
pure { values =>
Indexable[S].zipWithIndex(values).traverse {
case (value, index) =>
values.traverseWithIndex { (value, index) =>
rule.prefix(index).apply(value)
}
}
Expand Down
2 changes: 1 addition & 1 deletion checklist/src/main/scala/checklist/syntax.scala
@@ -1,3 +1,3 @@
package checklist

object syntax extends Rule1Syntax with MessageSyntax with CheckedSyntax
object syntax extends Rule1Syntax with MessageSyntax with CheckedSyntax with IndexableSyntax
3 changes: 2 additions & 1 deletion checklist/src/test/scala/checklist/RuleSpec.scala
@@ -1,6 +1,7 @@
package checklist

import cats.data.Ior
import cats.implicits._
import org.scalatest._
import monocle.macros.Lenses
import Rule._
Expand Down Expand Up @@ -118,7 +119,7 @@ class PropertyRulesSpec extends FreeSpec with Matchers {
}

"lengthLt" in {
val rule = lengthLt[String](5, errors("fail"))
val rule = lengthLt(5, errors("fail"))

rule("") should be(Ior.right(""))
rule("abcd") should be(Ior.right("abcd"))
Expand Down

0 comments on commit b219956

Please sign in to comment.