Skip to content

Commit

Permalink
woof
Browse files Browse the repository at this point in the history
  • Loading branch information
stew committed Sep 19, 2015
0 parents commit e5a9b1a
Show file tree
Hide file tree
Showing 12 changed files with 1,475 additions and 0 deletions.
49 changes: 49 additions & 0 deletions COPYING
@@ -0,0 +1,49 @@
Dogs Copyright (c) 2015 Michael (stew) O'Connor.

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

------

Code in Dogs is derived in part from Scalaz. The Scalaz license follows:

Copyright (c) 2009-2014 Tony Morris, Runar Bjarnason, Tom Adams,
Kristian Domagala, Brad Clow, Ricky Clarkson, Paul Chiusano, Trygve
Laugstøl, Nick Partridge, Jason Zaugg. All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. The name of the author may not be used to endorse or promote products
derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
17 changes: 17 additions & 0 deletions README.md
@@ -0,0 +1,17 @@
## Dogs

This project is intended to be a companion to the
[Cats project](https://github.com/non/cats).

It intents to be a library containing data structures which facilitate
pure functional programming in the Scala programming language. Some of
these are replacements for structures already present in the Scala
standard library, but with improvements in safety, some are data
structures for which there is no analogue in the Scala standard
library.

### EXPERIMENTAL

This project is in its infancy, it should be considered pre-alpha, it
might never go anywhere, it could change drastically along the way if
it does go somewhere.
4 changes: 4 additions & 0 deletions TODO
@@ -0,0 +1,4 @@
DList
Zipper
FingerTree
Map
24 changes: 24 additions & 0 deletions build.sbt
@@ -0,0 +1,24 @@
name := "dogs"

scalaVersion := "2.11.2"

scalacOptions := Seq(
"-deprecation",
"-encoding", "utf8",
"-language:postfixOps",
"-language:higherKinds",
"-target:jvm-1.7",
"-unchecked",
"-Xcheckinit",
"-Xfuture",
"-Xlint",
"-Xfatal-warnings",
"-Yno-adapted-args",
"-Ywarn-dead-code",
"-Ywarn-value-discard")

libraryDependencies ++= Seq(
"org.spire-math" %% "cats" % "0.3.0-SNAPSHOT",
"org.scalacheck" %% "scalacheck" % "1.12.5" % "test",
compilerPlugin("org.spire-math" %% "kind-projector" % "0.6.3")
)
1 change: 1 addition & 0 deletions project/build.properties
@@ -0,0 +1 @@
sbt.version=0.13.8
16 changes: 16 additions & 0 deletions project/plugins.sbt
@@ -0,0 +1,16 @@
resolvers += Resolver.url(
"tpolecat-sbt-plugin-releases",
url("http://dl.bintray.com/content/tpolecat/sbt-plugin-releases"))(
Resolver.ivyStylePatterns)

addSbtPlugin("com.eed3si9n" % "sbt-unidoc" % "0.3.2")
addSbtPlugin("com.github.gseitz" % "sbt-release" % "1.0.0")
addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.0.0")
addSbtPlugin("com.typesafe.sbt" % "sbt-ghpages" % "0.5.3")
addSbtPlugin("com.typesafe.sbt" % "sbt-site" % "0.8.1")
addSbtPlugin("org.tpolecat" % "tut-plugin" % "0.4.0")
addSbtPlugin("pl.project13.scala"% "sbt-jmh" % "0.2.3")
addSbtPlugin("org.scalastyle" %% "scalastyle-sbt-plugin" % "0.6.0")
addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.2.0")
addSbtPlugin("com.typesafe.sbt" % "sbt-git" % "0.8.4")
addSbtPlugin("org.scala-js" % "sbt-scalajs" % "0.6.5")
262 changes: 262 additions & 0 deletions src/main/scala/Dequeue.scala
@@ -0,0 +1,262 @@
package dogs

import cats._
import cats.data._

/**
* A Double-ended queue, based on the Bankers Double Ended Queue as
* described by C. Okasaki in "Purely Functional Data Structures"
*
* A queue that allows items to be put onto either the front (cons)
* or the back (snoc) of the queue in constant time, and constant
* time access to the element at the very front or the very back of
* the queue. Dequeueing an element from either end is constant time
* when amortized over a number of dequeues.
*
* This queue maintains an invariant that whenever there are at least
* two elements in the queue, neither the front list nor back list
* are empty. In order to maintain this invariant, a dequeue from
* either side which would leave that side empty constructs the
* resulting queue by taking elements from the opposite side
*/
sealed abstract class Dequeue[A] {
import Maybe._
import IList._
import Dequeue._

def isEmpty: Boolean

def frontMaybe: Maybe[A]
def backMaybe: Maybe[A]

/**
* dequeue from the front of the queue
*/
def uncons: Maybe[(A, Dequeue[A])] = this match {
case EmptyDequeue() => Maybe.notThere
case SingletonDequeue(a) => There((a, EmptyDequeue()))
case FullDequeue(OneAnd(f, INil()), 1, OneAnd(single, nil), 1) => There((f, SingletonDequeue(single)))
case FullDequeue(OneAnd(f, INil()), 1, OneAnd(x, ICons(xx, xs)), bs) => {
val xsr = reverseNEL(OneAnd(xx, xs))
There((f, FullDequeue(xsr, bs-1, OneAnd(x, INil()), 1)))
}
case FullDequeue(OneAnd(f, ICons(ff, fs)), s, back, bs) => There((f, FullDequeue(OneAnd(ff, fs), s-1, back, bs)))
}

/**
* dequeue from the back of the queue
*/
def unsnoc: Maybe[(A, Dequeue[A])] = this match {
case EmptyDequeue() => Maybe.notThere
case SingletonDequeue(a) => There((a, EmptyDequeue()))
case FullDequeue(OneAnd(single, INil()), 1, OneAnd(b, INil()), 1) => There((b, SingletonDequeue(single)))
case FullDequeue(OneAnd(x, ICons(xx,xs)), fs, OneAnd(b, INil()), 1) => {
val xsr = reverseNEL(OneAnd(xx, xs))
There((b, FullDequeue(OneAnd(x, INil()), 1, xsr, fs-1)))
}

case FullDequeue(front, fs, OneAnd(b, ICons(bb,bs)), s) => There((b, FullDequeue(front, fs, OneAnd(bb,bs), s-1)))
}

/**
* enqueue to the front of the queue
*/
def cons(a: A): Dequeue[A] = this match {
case EmptyDequeue() => SingletonDequeue(a)
case SingletonDequeue(single) => FullDequeue(OneAnd(a, INil()), 1, OneAnd(single, INil()), 1 )
case FullDequeue(front, fs, back, bs) => FullDequeue(OneAnd(a, ICons(front.head, front.tail)), fs+1, back, bs)
}

/**
* enqueue on to the back of the queue
*/
def snoc(a: A): Dequeue[A] = this match {
case EmptyDequeue() => SingletonDequeue(a)
case SingletonDequeue(single) => FullDequeue(OneAnd(single, INil[A]()), 1, OneAnd(a, INil[A]()), 1 )
case FullDequeue(front, fs, back, bs) => FullDequeue(front, fs, OneAnd(a, ICons(back.head, back.tail)), bs+1)
}

/**
* alias for cons
*/
def +:(a: A): Dequeue[A] = cons(a)

/**
* alias for snoc
*/
def :+(a: A): Dequeue[A] = snoc(a)

/**
* convert this queue to a stream of elements from front to back
*/
def toStreaming: Streaming[A] = streaming.unfold(this)(_.uncons)

/**
* convert this queue to a stream of elements from back to front
*/
def toBackStream: Streaming[A] = streaming.unfold(this)(_.unsnoc)

/**
* convert this queue to a list of elements from front to back
*/
def toIList: IList[A] = this match {
case EmptyDequeue() => INil()
case SingletonDequeue(a) => ICons(a, INil())
case FullDequeue(front, fs, back, bs) => front.head +: (front.tail ++ (back.tail reverse_::: ICons(back.head, INil())))
}

/**
* convert this queue to a list of elements from back to front
*/
def toBackIList: IList[A] = this match {
case EmptyDequeue() => INil()
case SingletonDequeue(a) => ICons(a, INil())
case FullDequeue(front, fs, back, bs) => back.head +: (back.tail ++ (front.tail.reverse ++ (ICons(front.head, INil()))))
}

/**
* Append another Deuque to this dequeue
*/
def ++(other: Dequeue[A]): Dequeue[A] = this match {
case EmptyDequeue() => other
case SingletonDequeue(a) => a +: other
case FullDequeue(f,fs,b,bs) => other match {
case EmptyDequeue() => this
case SingletonDequeue(a) => this :+ a
case FullDequeue(of,ofs,ob,obs) =>
FullDequeue(OneAnd(f.head, (f.tail ++
((b.head +: b.tail) reverse_:::
ICons(of.head, of.tail)))),
fs + bs + ofs,
ob,
obs)
}
}

def foldLeft[B](b: B)(f: (B,A) => B): B = this match {
case EmptyDequeue() => b
case SingletonDequeue(a) => f(b, a)
case FullDequeue(front,_,back,_) => {
val frontb = front.tail.foldLeft(f(b,front.head))(f)
val backb = back.tail.foldRight(frontb)((a, b) => f(b,a))
f(backb,back.head)
}
}

def foldRight[B](b: B)(f: (A,B) => B): B = this match {
case EmptyDequeue() => b
case SingletonDequeue(a) => f(a, b)
case FullDequeue(front,_,back,_) => {
val backb = back.tail.foldLeft(f(back.head, b))((b,a) => f(a,b))
val frontb = front.tail.foldRight(backb)(f)
f(front.head, frontb)
}
}


def map[B](f: A => B): Dequeue[B] = {
this match {
case EmptyDequeue() => EmptyDequeue()
case SingletonDequeue(a) => SingletonDequeue(f(a))
case FullDequeue(front, fs, back, bs) => {
val F = Functor[NEL]
FullDequeue(F.map(front)(f), fs, F.map(back)(f), bs)
}
}
}

def size: Int = this match {
case EmptyDequeue() => 0
case SingletonDequeue(_) => 1
case FullDequeue(_, fs, _, bs) => fs + bs
}

def reverse: Dequeue[A] = this match {
case FullDequeue(front, fs, back, bs) => FullDequeue(back, bs, front, fs)
case x => x
}
}

object Dequeue extends DequeueInstances {
type NEL[A] = OneAnd[A, IList]

def apply[A](as: A*) = as.foldLeft[Dequeue[A]](empty)((q,a) q :+ a)

def fromFoldable[F[_],A](fa: F[A])(implicit F: Foldable[F]): Dequeue[A] =
F.foldLeft[A,Dequeue[A]](fa,empty)((q,a) q :+ a)

def empty[A]: Dequeue[A] = EmptyDequeue()

private def reverseNEL[A](fa: NEL[A]): NEL[A] = {
@annotation.tailrec
def loop(xs: IList[A], acc: IList[A]): NEL[A] =
(xs: @unchecked) match {
case ICons(h, INil()) =>
OneAnd(h, acc)
case ICons(h, t) =>
loop(t, h :: acc)
}
loop(fa.head :: fa.tail, INil())
}
}

/**
* special case of the queue when it contains just a single element
* which can be accessed from either side of the queue
*/
private[dogs] final case class SingletonDequeue[A](single: A) extends Dequeue[A] {
override def isEmpty = false
override def frontMaybe = Maybe.there(single)
override def backMaybe = Maybe.there(single)
}

/**
* a queue which has at least two elements, it is guaranteed that the
* front list and back lists cannot be empty
*/
private[dogs] final case class FullDequeue[A](front: OneAnd[A, IList], fsize: Int, back: OneAnd[A, IList], backSize: Int) extends Dequeue[A] {
override def isEmpty = false
override def frontMaybe = Maybe.there(front.head)
override def backMaybe = Maybe.there(back.head)
}
/**
* a queue which has no elements
*/
private[dogs] final case object EmptyDequeue extends Dequeue[Nothing] {
override val isEmpty = true
override val frontMaybe = Maybe.notThere
override val backMaybe = Maybe.notThere

def apply[A]() = this.asInstanceOf[Dequeue[A]]
def unapply[A](q: Dequeue[A]) = q.isEmpty
}

sealed abstract class DequeueInstances {
implicit def equalDequeue[A](implicit A0: Eq[A]): Eq[Dequeue[A]] =
new DequeueEqual[A] {
val A = A0
}

implicit def dequeueMonoid[A]: Monoid[Dequeue[A]] = new Monoid[Dequeue[A]] {
def empty: Dequeue[A] = Dequeue.empty
def combine(a: Dequeue[A], b :Dequeue[A]): Dequeue[A] = a ++ b
}

implicit val dequeueInstances: Foldable[Dequeue] with Functor[Dequeue] = new Foldable[Dequeue] with Functor[Dequeue] {
override def foldRight[A,B](fa: Dequeue[A], b: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] = fa.foldRight(b)((a,b) => f(a,b))


override def foldLeft[A,B](fa: Dequeue[A], b: B)(f: (B,A)=>B): B = fa.foldLeft(b)(f)
override def foldMap[A,B](fa: Dequeue[A])(f: A => B)(implicit F: Monoid[B]): B = fa.foldLeft(F.empty)((b,a) => F.combine(b, f(a)))
override def map[A,B](fa: Dequeue[A])(f: A => B): Dequeue[B] = fa map f
}
}

private[dogs] trait DequeueEqual[A] extends Eq[Dequeue[A]] {
implicit def A: Eq[A]
import std.stream._

final override def eqv(a: Dequeue[A], b: Dequeue[A]): Boolean =
Eq[Streaming[A]].eqv(a.toStreaming, b.toStreaming)
}

0 comments on commit e5a9b1a

Please sign in to comment.