Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add some new refolds. #27

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions CHANGELOG/breaking.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
- removed `FunctorT.translate` (use either `.transCata` or `.transAna` instead)
- fixed the type param order on `IdOps.ghylo`

Non-breaking changes:
- added `ghyloM`, `dyna`, `codyna`, `codynaM` refolds
- added a `ganaM` unfold
- added `Nat` to `matryoshka.fixedpoint`
- added transform type aliases
- added optics for algebras and folds
26 changes: 13 additions & 13 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,15 @@ val scalazVersion = "7.2.1"
val specs2Version = "3.7"

val testDependencies = libraryDependencies ++= Seq(
"com.github.julien-truffaut" %% "monocle-law" % monocleVersion % "test",
"org.typelevel" %% "discipline" % "0.4" % "test",
"org.scalaz" %% "scalaz-scalacheck-binding" % scalazVersion % "test",
"org.specs2" %% "specs2-core" % specs2Version % "test" force(),
"org.specs2" %% "specs2-scalacheck" % specs2Version % "test" force(),
// `scalaz-scalacheck-binding` is built with `scalacheck` 1.12.5 so we are stuck with that version
"org.scalacheck" %% "scalacheck" % "1.12.5" % "test" force()
"org.typelevel" %% "discipline" % "0.4" % "test",
"com.github.julien-truffaut" %% "monocle-law" % monocleVersion % "test",
"org.scalaz" %% "scalaz-scalacheck-binding" % scalazVersion % "test",
"org.typelevel" %% "scalaz-specs2" % "0.4.0" % "test",
"org.specs2" %% "specs2-core" % specs2Version % "test" force(),
"org.specs2" %% "specs2-scalacheck" % specs2Version % "test" force(),
// `scalaz-scalack-binding` is built with `scalacheck` 1.12.5 so we are stuck
// with that version
"org.scalacheck" %% "scalacheck" % "1.12.5" % "test" force()
)

lazy val standardSettings = Seq(
Expand Down Expand Up @@ -66,17 +68,15 @@ lazy val standardSettings = Seq(
"-Ywarn-value-discard"),
scalacOptions in (Test, console) --= Seq(
"-Yno-imports",
"-Ywarn-unused-import"
),
"-Ywarn-unused-import"),
wartremoverErrors in (Compile, compile) ++= warts, // Warts.all,

console <<= console in Test, // console alias test:console

libraryDependencies ++= Seq(
"com.github.julien-truffaut" %%% "monocle-core" % monocleVersion % "compile, test",
"com.github.mpilquist" %%% "simulacrum" % "0.7.0" % "compile, test",
"org.scalaz" %%% "scalaz-core" % scalazVersion % "compile, test"
),
"com.github.julien-truffaut" %%% "monocle-core" % monocleVersion % "compile, test",
"org.scalaz" %%% "scalaz-core" % scalazVersion % "compile, test",
"com.github.mpilquist" %%% "simulacrum" % "0.7.0" % "compile, test"),

licenses += ("Apache 2", url("http://www.apache.org/licenses/LICENSE-2.0")),

Expand Down
1 change: 0 additions & 1 deletion core/jvm/src/test/scala/matryoshka/example/Example.scala
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ import org.scalacheck._
import org.specs2.ScalaCheck
import org.specs2.mutable._
import scalaz._, Scalaz._
import scalaz.scalacheck.ScalaCheckBinding._
import scalaz.scalacheck.ScalazProperties._

sealed trait Example[A]
Expand Down
87 changes: 70 additions & 17 deletions core/jvm/src/test/scala/matryoshka/helpers/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,16 @@

package matryoshka

import scala.Predef.implicitly
import scala.{None, Option, Some}

import monocle.law.discipline._
import org.scalacheck._
import org.specs2.mutable._
import org.typelevel.discipline.specs2.mutable._
import scalaz._, Scalaz._
import scalaz.scalacheck.ScalaCheckBinding._
import scalaz.scalacheck.ScalaCheckBinding.{GenMonad => _, _}

package object helpers {
package object helpers extends Specification with Discipline {
implicit def NTArbitrary[F[_], A](
implicit A: Arbitrary[A], F: Arbitrary ~> λ[α => Arbitrary[F[α]]]):
Arbitrary[F[A]] =
Expand All @@ -39,11 +42,10 @@ package object helpers {
Arbitrary(Gen.resize(size - 1, corecArbitrary[T, F].arbitrary))).arbitrary.map(_.embed)))

implicit def freeArbitrary[F[_]](
implicit F: Arbitrary ~> λ[α => Arbitrary[F[α]]]):
Arbitrary ~> λ[α => Arbitrary[Free[F, α]]] =
new (Arbitrary ~> λ[α => Arbitrary[Free[F, α]]]) {
implicit F: Arbitrary ~> (Arbitrary ∘ F)#λ):
Arbitrary ~> (Arbitrary ∘ Free[F, ?])#λ =
new (Arbitrary ~> (Arbitrary ∘ Free[F, ?])#λ) {
def apply[α](arb: Arbitrary[α]) =
// FIXME: This is only generating leaf nodes
Arbitrary(Gen.sized(size =>
if (size <= 1)
arb.map(_.point[Free[F, ?]]).arbitrary
Expand All @@ -53,16 +55,67 @@ package object helpers {
F(freeArbitrary[F](F)(arb)).arbitrary.map(Free.roll))))
}

implicit def freeEqual[F[_]: Functor](
implicit F: Equal ~> λ[α => Equal[F[α]]]):
Equal ~> λ[α => Equal[Free[F, α]]] =
new (Equal ~> λ[α => Equal[Free[F, α]]]) {
def apply[α](eq: Equal[α]) =
Equal.equal((a, b) => (a.resume, b.resume) match {
case (-\/(f1), -\/(f2)) =>
F(freeEqual[F](implicitly, F)(eq)).equal(f1, f2)
case (\/-(a1), \/-(a2)) => eq.equal(a1, a2)
implicit def cofreeArbitrary[F[_]](
implicit F: Arbitrary ~> (Arbitrary ∘ F)#λ):
Arbitrary ~> (Arbitrary ∘ Cofree[F, ?])#λ =
new (Arbitrary ~> (Arbitrary ∘ Cofree[F, ?])#λ) {
def apply[A](arb: Arbitrary[A]) =
Arbitrary(Gen.sized(size =>
if (size <= 0)
Gen.fail[Cofree[F, A]]
else (arb.arbitrary ⊛ F(cofreeArbitrary(F)(arb)).arbitrary)(Cofree(_, _))))
}

implicit def optionArbitrary: Arbitrary ~> (Arbitrary ∘ Option)#λ =
new (Arbitrary ~> (Arbitrary ∘ Option)#λ) {
def apply[A](arb: Arbitrary[A]) =
Arbitrary(Gen.frequency(
( 1, None.point[Gen]),
(75, arb.arbitrary.map(_.some))))
}

implicit def optionEqualNT: Equal ~> (Equal ∘ Option)#λ =
new (Equal ~> (Equal ∘ Option)#λ) {
def apply[A](eq: Equal[A]) =
Equal.equal {
case (None, None) => true
case (Some(a), Some(b)) => eq.equal(a, b)
case (_, _) => false
})
}
}

implicit def eitherArbitrary[A: Arbitrary]:
Arbitrary ~> (Arbitrary ∘ (A \/ ?))#λ =
new (Arbitrary ~> (Arbitrary ∘ (A \/ ?))#λ) {
def apply[B](arb: Arbitrary[B]) =
Arbitrary(Gen.oneOf(
Arbitrary.arbitrary[A].map(-\/(_)),
arb.arbitrary.map(\/-(_))))
}

implicit def nonEmptyListArbitrary:
Arbitrary ~> (Arbitrary ∘ NonEmptyList)#λ =
new (Arbitrary ~> (Arbitrary ∘ NonEmptyList)#λ) {
def apply[A](arb: Arbitrary[A]) =
Arbitrary((arb.arbitrary ⊛ Gen.listOf[A](arb.arbitrary))((h, t) =>
NonEmptyList.nel(h, t.toIList)))
}

implicit def nonEmptyListEqual: Equal ~> (Equal ∘ NonEmptyList)#λ =
new (Equal ~> (Equal ∘ NonEmptyList)#λ) {
def apply[A](eq: Equal[A]) = NonEmptyList.nonEmptyListEqual(eq)
}


def checkAlgebraIsoLaws[F[_], A](iso: AlgebraIso[F, A])(
implicit FA: Arbitrary ~> (Arbitrary ∘ F)#λ, AA: Arbitrary[A], FE: Equal ~> (Equal ∘ F)#λ, AE: Equal[A]) =
checkAll("algebra Iso", IsoTests(iso))

def checkAlgebraPrismLaws[F[_], A](prism: AlgebraPrism[F, A])(
implicit FA: Arbitrary ~> (Arbitrary ∘ F)#λ, AA: Arbitrary[A], FE: Equal ~> (Equal ∘ F)#λ, AE: Equal[A]) =
checkAll("algebra Prism", PrismTests(prism))

def checkCoalgebraPrismLaws[F[_], A](prism: CoalgebraPrism[F, A])(
implicit FA: Arbitrary ~> (Arbitrary ∘ F)#λ, AA: Arbitrary[A], FE: Equal ~> (Equal ∘ F)#λ, AE: Equal[A]) =
checkAll("coalgebra Prism", PrismTests(prism))
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,10 @@ import scala.Int

import org.specs2.ScalaCheck
import org.specs2.mutable._
import org.specs2.scalaz.{ScalazMatchers}
import scalaz._, Scalaz._

class ListSpec extends Specification with ScalaCheck with specs2.scalaz.Matchers {
class ListSpec extends Specification with ScalaCheck with ScalazMatchers {
"apply" should {
"be equivalent to scala.List.apply" in {
List(1, 2, 3, 4).cata(ListF.listIso.get) must
Expand Down
27 changes: 27 additions & 0 deletions core/jvm/src/test/scala/matryoshka/instances/fixedpoint/nat.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* Copyright 2014–2016 SlamData Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package matryoshka.instances.fixedpoint

import matryoshka._
import matryoshka.helpers._

import org.specs2.mutable._
import scalaz._, Scalaz._

class NatSpec extends Specification {
checkCoalgebraPrismLaws(Nat.intPrism)
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,37 +16,95 @@

package matryoshka.instances.fixedpoint

import matryoshka._
import matryoshka._, Recursive.ops._, FunctorT.ops._
import matryoshka.helpers._
import matryoshka.specs2.scalacheck.CheckAll

import scala.Predef.implicitly
import scala.Int
import scala.Predef.{implicitly}
import scala.{Int}

import org.scalacheck._
import org.specs2.ScalaCheck
import org.specs2.mutable._
import org.specs2.scalaz.{ScalazMatchers}
import scalaz._, Scalaz._
import scalaz.scalacheck.ScalazProperties._
import scalaz.scalacheck.{ScalazProperties => Props}

class PartialSpec extends Specification with ScalaCheck with CheckAll {
implicit def eitherArbitrary[A: Arbitrary]:
Arbitrary ~> λ[β => Arbitrary[A \/ β]] =
new (Arbitrary ~> λ[β => Arbitrary[A \/ β]]) {
def apply[β](arb: Arbitrary [β]) =
Arbitrary(Gen.oneOf(
Arbitrary.arbitrary[A].map(-\/(_)),
arb.arbitrary.map(\/-(_))))
}
// TODO: Figure out how to implement Monads generically for fixed-point types,
// and get them scoped automatically (companion for a type alias doesn’t
// work)
import Partial.monad

class PartialSpec extends Specification with ScalazMatchers with ScalaCheck with CheckAll {
/** For testing cases that should work with truly diverging functions. */
def sometimesNeverGen[A: Arbitrary]: Gen[Partial[A]] =
Gen.oneOf(Arbitrary.arbitrary[Partial[A]], Gen.const(Partial.never[A]))

"Partial" should {
"satisfy relevant laws" in {
checkAll(equal.laws[Partial[Int]](Partial.equal, implicitly))
checkAll(monad.laws[Partial](Partial.monad, implicitly, implicitly, implicitly, Partial.equal))
checkAll(Props.equal.laws[Partial[Int]](Partial.equal, implicitly))
checkAll(Props.monad.laws[Partial](implicitly, implicitly, implicitly, implicitly, Partial.equal))
}
}

// https://en.wikipedia.org/wiki/McCarthy_91_function
def mc91(n: Int): Partial[Int] =
if (n > 100) Partial.now(n - 10)
else mc91(n + 11) >>= mc91

"never" should {
"always have more steps" in {
Partial.never[Int].runFor(1000) must beRightDisjunction
}
}

"runFor" should {
"return now immediately" in {
Partial.now(13).runFor(0) must beLeftDisjunction(13)
}

"return a value when it runs past the end" in {
Partial.later(Partial.now(7)).runFor(300000) must beLeftDisjunction(7)
}

"return after multiple runs" ! prop { (a: Conat, b: Conat) =>
val (ai, bi) = (a.cata(height), b.cata(height))
bi > 0 ==> {
val first = (a + b).transAna(Partial.delay(27)).runFor(ai)
first must beRightDisjunction
first.flatMap(_.runFor(bi)) must beLeftDisjunction(27)
}
}

"still pending one short" ! prop { (a: Conat) =>
val ai = a.cata(height)
ai > 0 ==> {
val first = a.transAna(Partial.delay(27)).runFor(ai - 1)
first must beRightDisjunction
first.flatMap(_.runFor(1)) must beLeftDisjunction(27)
}
}

"return exactly at the end" ! prop { (i: Conat) =>
i.transAna(Partial.delay(4)).runFor(i.cata(height)) must
beLeftDisjunction(4)
}
}

"unsafePerformSync" should {
"return now immediately" in {
Partial.now(12).unsafePerformSync must equal(12)
}

"return a value when it gets to the end" in {
Partial.later(Partial.later(Partial.now(3))).unsafePerformSync must
equal(3)
}

// TODO: Should work with any Int, but stack overflows on big negatives.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the implementation of unsafePerformSync, have you tried making it @tailrec by explicitly match-ing on the disjunction rather than fold-ing? I'm not sure if the Ops class gets in the way of scalac's "tco", but might work.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you’re right that it should be @tailrec, but this actually overflows on the non-tailrecable mc91 (but feel free to tell me how I could make it tailrec).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, not sure how to make mc91 tailrec, maybe Trampoline to avoid overflow? (No idea if that is possible with Partial either!)

"always terminate with mc91" ! prop { (n: Nat) =>
val i = n.cata(height)
mc91(i).unsafePerformSync must equal(if (i <= 100) 91 else i - 10)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,10 @@ import scala.Int

import org.specs2.ScalaCheck
import org.specs2.mutable._
import org.specs2.scalaz.{ScalazMatchers}
import scalaz._, Scalaz._

class StreamSpec extends Specification with ScalaCheck with specs2.scalaz.Matchers {
class StreamSpec extends Specification with ScalaCheck with ScalazMatchers {
/** Infinite sequence of Fibonacci numbers (at least until they overflow
* int32)
*/
Expand Down Expand Up @@ -60,12 +61,12 @@ class StreamSpec extends Specification with ScalaCheck with specs2.scalaz.Matche
// FIXME: These two blow up the stack with much larger inputs

"have the given value at an arbitrary point" ! prop { (i: Int, d: Int) =>
i.ana[Nu, (Int, ?)](constantly).drop(20000).head must equal(i)
i.ana[Nu, (Int, ?)](constantly).drop(30000).head must equal(i)
}

"have subsequence of the given value" ! prop { (i: Int, t: Int) =>
i.ana[Nu, (Int, ?)](constantly).take(900) must
equal(List.fill(900)(i))
i.ana[Nu, (Int, ?)](constantly).take(450) must
equal(List.fill(450)(i))
}
}
}
Loading