├── project ├── build.properties ├── plugins.sbt └── build.scala ├── .gitignore ├── tests └── src │ └── test │ └── scala │ └── bound │ ├── BoundProperties.scala │ ├── SerializationTests.scala │ └── ScopeTests.scala ├── core └── src │ └── main │ └── scala │ └── bound │ ├── Var.scala │ ├── Equal1.scala │ ├── Bound.scala │ ├── Name.scala │ ├── package.scala │ └── Scope.scala ├── f0-binding └── src │ └── main │ └── scala │ └── bound │ └── BoundSerialization.scala ├── LICENSE ├── scalacheck-binding └── src │ └── main │ └── scala │ └── bound │ └── scalacheck │ └── BoundArbitraryInstances.scala ├── README.md ├── examples └── src │ └── main │ └── scala │ └── exp │ └── Exp.scala └── sbt /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=0.13.8 2 | -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | resolvers += DefaultMavenRepository 2 | 3 | addSbtPlugin("me.lessis" % "bintray-sbt" % "0.3.0") 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | *.log 3 | *~ 4 | 5 | # sbt specific 6 | dist/* 7 | target/ 8 | lib_managed/ 9 | src_managed/ 10 | .lib/ 11 | 12 | # Scala-IDE specific 13 | .scala_dependencies 14 | 15 | # vim-specific 16 | tags 17 | -------------------------------------------------------------------------------- /tests/src/test/scala/bound/BoundProperties.scala: -------------------------------------------------------------------------------- 1 | package bound 2 | 3 | import org.scalacheck.Properties 4 | import org.scalacheck.Prop 5 | import org.scalacheck.Prop._ 6 | 7 | abstract class BoundProperties(name: String) extends Properties(name){ 8 | 9 | def test(name:String)(f: => Prop) = property(name) = secure { 10 | try f catch { case e: java.lang.Throwable => e.printStackTrace(System.err); throw e } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /core/src/main/scala/bound/Var.scala: -------------------------------------------------------------------------------- 1 | package bound 2 | 3 | import scalaz._ 4 | 5 | /** 6 | * A `Var[B,F]` is a variable that may either be "bound" (`B`) or "free" (`F`). 7 | * It is really a type alias for `scalaz.\/`. 8 | */ 9 | object F { 10 | def apply[B,F](v: F): Var[B,F] = \/-(v).asInstanceOf[Var[B,F]] 11 | def unapply[B,F](v: Var[B,F]): Option[F] = v.toOption 12 | } 13 | 14 | object B { 15 | def apply[B,F](v: B): Var[B,F] = -\/(v).asInstanceOf[Var[B,F]] 16 | def unapply[B,F](v: Var[B,F]): Option[B] = v.fold(Some(_), _ => None) 17 | } 18 | -------------------------------------------------------------------------------- /core/src/main/scala/bound/Equal1.scala: -------------------------------------------------------------------------------- 1 | package bound 2 | 3 | import scalaz.{Equal, Scalaz} 4 | import Scalaz._ 5 | 6 | trait Equal1[F[_]]{ 7 | def equal[A](a1: F[A], a2: F[A])(implicit eq0: Equal[A]): Boolean 8 | } 9 | 10 | object Equal1 { 11 | implicit val Equal1List: Equal1[List] = new Equal1[List] { 12 | def equal[A](a1: List[A], a2: List[A])(implicit a: Equal[A]): Boolean = implicitly[Equal[List[A]]].equal(a1, a2) 13 | } 14 | 15 | implicit val Equal1Option: Equal1[Option] = new Equal1[Option] { 16 | def equal[A](a1: Option[A], a2: Option[A])(implicit a: Equal[A]): Boolean = implicitly[Equal[Option[A]]].equal(a1, a2) 17 | } 18 | } -------------------------------------------------------------------------------- /core/src/main/scala/bound/Bound.scala: -------------------------------------------------------------------------------- 1 | package bound 2 | 3 | import scalaz._ 4 | 5 | /** 6 | * A class for performing substitution into things that are not necessarily 7 | * monad transformers. 8 | */ 9 | trait Bound[T[_[_],_]] { 10 | /** Perform substitution. */ 11 | def bind[F[_]:Monad,A,C](m: T[F, A])(f: A => F[C]): T[F, C] 12 | } 13 | 14 | object Bound { 15 | /** 16 | * If `T` is a monad transformer then this is the default implementation: 17 | * `bind(m)(f) = m.flatMap(x => T.liftM(f(x)))` 18 | */ 19 | implicit def defaultBound[T[_[_],_]:MonadTrans]: Bound[T] = new Bound[T] { 20 | def bind[F[_]:Monad,A,C](m: T[F, A])(f: A => F[C]) = { 21 | val T = MonadTrans[T] 22 | T.apply[F].bind(m)(a => T.liftM(f(a))) 23 | } 24 | } 25 | } 26 | 27 | trait BoundOps[T[_[_],_], F[_], A] { 28 | def self: T[F, A] 29 | implicit def T: Bound[T] 30 | 31 | final def >>>=[C](f: A => F[C])(implicit F: Monad[F]) = 32 | T.bind(self)(f) 33 | 34 | final def =<<<:[C](f: A => F[C])(implicit F: Monad[F]) = 35 | >>>=[C](f) 36 | } 37 | 38 | -------------------------------------------------------------------------------- /f0-binding/src/main/scala/bound/BoundSerialization.scala: -------------------------------------------------------------------------------- 1 | package bound 2 | 3 | import f0._ 4 | import Writers._ 5 | import Readers._ 6 | 7 | object BoundSerialization { 8 | 9 | def varW[B,F1,F,F2](wb: Writer[B,F1], wf: Writer[F,F2]): Writer[Var[B,F],S2[F1,F2]] = 10 | s2W(wb,wf)((a,b) => _.fold(a,b)) 11 | 12 | def varR[B,F1,F,F2](rb: Reader[B,F1], rf: Reader[F,F2]): Reader[Var[B,F],S2[F1,F2]] = 13 | eitherR(rb, rf).map(scalaz.\/.fromEither) 14 | 15 | trait Writer1[F[+_]] { def apply[A](wa: Writer[A, DynamicF]): Writer[F[A], DynamicF] } 16 | trait Reader1[F[+_]] { def apply[A](wa: Reader[A, DynamicF]): Reader[F[A], DynamicF] } 17 | 18 | def scopeW[B, BF, F[+_], V, VF]( 19 | pb: Writer[B, BF], 20 | pf: Writer1[F], 21 | pv: Writer[V, VF] 22 | ): Writer[Scope[B, F, V], DynamicF] = 23 | pf(varW(pb.erase, pf(pv.erase)).erase).cmap((s: Scope[B, F, V]) => s.unscope) 24 | 25 | def scopeR[B, BF, F[+_], V, VF]( 26 | pb: Reader[B, BF], 27 | pf: Reader1[F], 28 | pv: Reader[V, VF] 29 | ): Reader[Scope[B, F, V], DynamicF] = 30 | pf(varR(pb.erase, pf(pv.erase)).erase).map(Scope(_)) 31 | } 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Rúnar Óli Bjarnason, Josh Cough, Edward Kmett 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /tests/src/test/scala/bound/SerializationTests.scala: -------------------------------------------------------------------------------- 1 | package bound 2 | 3 | import org.scalacheck.{Arbitrary, Prop} 4 | import org.scalacheck.Prop._ 5 | import bound.scalacheck.BoundArbitraryInstances._ 6 | import BoundSerialization._ 7 | import f0._ 8 | import f0.Readers._ 9 | import f0.Writers._ 10 | import f0.DynamicF 11 | import scalaz.{Equal, Scalaz} 12 | import Scalaz._ 13 | import Scope._ 14 | 15 | object SerializationTests extends BoundProperties("Scope Tests"){ 16 | 17 | test("var == put/get var")(clone(varW(intW, stringW), varR(intR, stringR))) 18 | test("scope == put/get scope")(clone(scopeW(intW, listW1, stringW), scopeR(intR, listR1, stringR))) 19 | 20 | lazy val listW1 = new Writer1[List] { 21 | def apply[A](wa: Writer[A, DynamicF]): Writer[List[A], DynamicF] = 22 | Writers.repeatW(wa).cmap((as: List[A]) => as.toTraversable).erase 23 | } 24 | 25 | lazy val listR1 = new Reader1[List] { 26 | def apply[A](wa: Reader[A, DynamicF]): Reader[List[A], DynamicF] = Readers.listR(wa).erase 27 | } 28 | 29 | def clone[A,F](w: Writer[A,F], r: Reader[A,F])(implicit eql: Equal[A], arb: Arbitrary[A]): Prop = 30 | forAll((a: A) => { 31 | import eql.equalSyntax._ 32 | (r(w.toByteArray(a)) === a) 33 | }) 34 | } 35 | -------------------------------------------------------------------------------- /tests/src/test/scala/bound/ScopeTests.scala: -------------------------------------------------------------------------------- 1 | package bound 2 | 3 | import org.scalacheck.Prop._ 4 | import bound.scalacheck.BoundArbitraryInstances._ 5 | import scalaz._ 6 | import Scalaz._ 7 | import Scope._ 8 | 9 | object ScopeTests extends BoundProperties("Scope Tests"){ 10 | 11 | test("== reflexivity for Scope (with List)")(forAll{ (scope:Scope[Int, List, String]) => eqls(scope, scope) }) 12 | test("== reflexivity for Scope (with Option)")(forAll{ (scope:Scope[Int, Option, String]) => eqls(scope, scope) }) 13 | 14 | def eqls[F[+_]](a:Scope[Int, F, String], b: Scope[Int, F, String])(implicit eql: Equal[Scope[Int, F, String]]) = { 15 | eql.equal(a, b) 16 | } 17 | 18 | object instances { 19 | def functor[F[_]: Functor, A] = Functor[({type λ[α] = Scope[A, F, α]})#λ] 20 | def foldable[F[_]: Foldable, A] = Foldable[({type λ[α] = Scope[A, F, α]})#λ] 21 | def traverse[F[_]: Traverse, A] = Traverse[({type λ[α] = Scope[A, F, α]})#λ] 22 | def monad[F[_]: Monad, A] = Monad[({type λ[α] = Scope[A, F, α]})#λ] 23 | 24 | // checking absence of ambiguity 25 | def foldable[F[_]: Traverse, A] = Foldable[({type λ[α] = Scope[A, F, α]})#λ] 26 | def functor[F[_]: Traverse, A] = Functor[({type λ[α] = Scope[A, F, α]})#λ] 27 | def functor[F[_]: Monad, A] = Functor[({type λ[α] = Scope[A, F, α]})#λ] 28 | def functor[F[_]: Traverse: Monad, A] = Functor[({type λ[α] = Scope[A, F, α]})#λ] 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /scalacheck-binding/src/main/scala/bound/scalacheck/BoundArbitraryInstances.scala: -------------------------------------------------------------------------------- 1 | package bound 2 | package scalacheck 3 | 4 | import org.scalacheck.{Gen, Arbitrary} 5 | import Arbitrary._ 6 | import Gen._ 7 | import scalaz._ 8 | import Scalaz._ 9 | import bound._ 10 | 11 | /** 12 | * Instances of {@link scalacheck.Arbitrary} for types in scala-bound. 13 | */ 14 | object BoundArbitraryInstances { 15 | 16 | private def arb[A: Arbitrary]: Arbitrary[A] = implicitly[Arbitrary[A]] 17 | 18 | implicit def ArbitraryVar[T, U](implicit b: Arbitrary[T], f: Arbitrary[U]): Arbitrary[Var[T, U]] = 19 | Arbitrary(oneOf(arbitrary[T] map ((t: T) => B(t)), arbitrary[U] map ((u: U) => F(u)))) 20 | 21 | trait Arbitrary1[F[_]] { 22 | def arbitrary1[A](implicit a: Arbitrary[A]): Arbitrary[F[A]] 23 | } 24 | 25 | implicit def ArbitraryScope[B,F[_],A](implicit AB: Arbitrary[B], AF: Arbitrary1[F], AA: Arbitrary[A]): Arbitrary[Scope[B,F,A]] = 26 | Arbitrary(AF.arbitrary1(ArbitraryVar(AB,AF.arbitrary1(AA))).arbitrary.map(Scope(_))) 27 | 28 | implicit val Arbitrary1List: Arbitrary1[List] = new Arbitrary1[List] { 29 | def arbitrary1[A](implicit a: Arbitrary[A]): Arbitrary[List[A]] = implicitly[Arbitrary[List[A]]] 30 | } 31 | 32 | implicit val Arbitrary1Maybe: Arbitrary1[Option] = new Arbitrary1[Option] { 33 | def arbitrary1[A](implicit a: Arbitrary[A]): Arbitrary[Option[A]] = implicitly[Arbitrary[Option[A]]] 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /core/src/main/scala/bound/Name.scala: -------------------------------------------------------------------------------- 1 | package bound 2 | 3 | import scalaz.{Name => _, _} 4 | import scalaz.syntax.monad._ 5 | import scalaz.syntax.equal._ 6 | import scalaz.syntax.show._ 7 | 8 | case class Name[N,B](name: N, value: B) 9 | 10 | object Name { 11 | // Type-class Instances // 12 | 13 | implicit def nameShow[N:Show,B:Show]: Show[Name[N,B]] = new Show[Name[N,B]] { 14 | override def shows(n: Name[N,B]) = "Name %s %s".format(n.name.shows, n.value.shows) 15 | } 16 | 17 | implicit def nameOrder[N, B: Order]: Order[Name[N,B]] = new Order[Name[N,B]] { 18 | def order(m: Name[N,B], n: Name[N,B]) = Order[B].order(m.value, n.value) 19 | } 20 | 21 | implicit def nameTraverse[N]: Traverse[({type λ[α]=Name[N,α]})#λ] = 22 | new Traverse[({type λ[α]=Name[N,α]})#λ] { 23 | def traverseImpl[F[_]:Applicative,A,B](n: Name[N,A])(f: A => F[B]): F[Name[N,B]] = 24 | Functor[F].map(f(n.value))(x => n.copy(value = x)) 25 | } 26 | 27 | implicit val nameBifunctor: Bifunctor[Name] = new Bifunctor[Name] { 28 | def bimap[A,B,C,D](n: Name[A,B])(f: A => C, g: B => D) = 29 | Name(f(n.name), g(n.value)) 30 | } 31 | 32 | implicit def nameComonad[N]: Comonad[({type λ[α]=Name[N,α]})#λ] = 33 | new Comonad[({type λ[α]=Name[N,α]})#λ] { 34 | def copoint[A](p: Name[N,A]) = p.value 35 | override def cojoin[A](p: Name[N,A]) = Name(p.name, p) 36 | def map[A,B](p: Name[N,A])(f: A => B) = Name(p.name, f(p.value)) 37 | def cobind[A,B](p: Name[N,A])(f: Name[N,A] => B) = 38 | Name(p.name, f(p)) 39 | } 40 | 41 | implicit def bitraverse: Bifoldable[Name] = new Bifoldable[Name] { 42 | def bifoldMap[A,B,M:Monoid](n: Name[A,B])(f: A => M)(g: B => M) = 43 | Monoid[M].append(f(n.name), g(n.value)) 44 | def bifoldRight[A,B,R](n: Name[A,B], z: => R)(f: (A, => R) => R)(g: (B, => R) => R) = 45 | f(n.name, g(n.value, z)) 46 | } 47 | } 48 | 49 | 50 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Bound 2 | ===== 3 | 4 | A library for developing languages with scoped binders (like forall or lambda). 5 | 6 | This is a Scala port of [Edward Kmett's Bound library for Haskell](https://github.com/ekmett/bound). 7 | 8 | Getting started 9 | --------------- 10 | 11 | ###Add this your build.sbt file:### 12 | 13 | ``` 14 | resolvers += "Runar's Bintray Repo" at "http://dl.bintray.com/runarorama/maven/" 15 | 16 | libraryDependencies += "bound" %% "bound-core" % "1.3.0" 17 | ``` 18 | 19 | Binary packages available at https://bintray.com/runarorama/maven/bound 20 | 21 | This library provides convenient combinators for working with "locally-nameless" terms. These can be useful when writing a type checker, evaluator, parser, or pretty-printer for terms that contain binders like forall or lambda. They ease the task of avoiding variable capture and testing for alpha-equivalence. 22 | 23 | Example 24 | ------- 25 | 26 | ```scala 27 | import scalaz._ 28 | import scalaz.std.string._ 29 | import bound._ 30 | 31 | // An untyped lambda calculus 32 | sealed trait Exp[+A] { 33 | def apply[B >: A](arg: Exp[B]): Exp[B] = App(this, arg) 34 | } 35 | case class V[+A](a: A) extends Exp[A] 36 | case class App[+A](fn: Exp[A], arg: Exp[A]) extends Exp[A] 37 | case class Lam[+A](s: Scope[Unit, Exp, A]) extends Exp[A] 38 | 39 | // A monad for our calculus 40 | implicit val expMonad: Monad[Exp] = new Monad[Exp] { 41 | def point[A](a: => A) = V(a) 42 | def bind[A,B](m: Exp[A])(f: A => Exp[B]): Exp[B] = m match { 43 | case V(a) => f(a) 44 | case App(x,y) => App(bind(x)(f), bind(y)(f)) 45 | case Lam(e) => Lam(e >>>= f) 46 | } 47 | } 48 | 49 | // Lambda abstraction smart constructor. 50 | def lam[A:Equal](v: A, b: Exp[A]): Exp[A] = 51 | Lam(abstract1(v, b)) 52 | 53 | // Weak-head normal form evaluator. 54 | def whnf[A](e: Exp[A]): Exp[A] = e match { 55 | case App(f,a) => whnf(f) match { 56 | case Lam(b) => whnf(instantiate1(a,b)) 57 | case g => App(g,a) 58 | } 59 | case e => e 60 | } 61 | ``` 62 | 63 | We can then construct and evaluate lambda terms in the console. The `const` function contains two nested scopes. Note that the variable names are erased. What remains are two nested scopes. 64 | The `-\/` indicates a term bound in the outer scope. The `\/-` indicates a free term. 65 | 66 | scala> val const = lam("x", lam("y", V("x"))) 67 | const: Exp[String] = Lam(Scope(Lam(Scope(V(\/-(V(-\/(())))))))) 68 | 69 | The part that is the outer scope is `Lam(Scope(...V(-\/(()))...))`. The inner scope is `...Lam(Scope(V(\/-(...))))...`. So what used to be `x` is bound in the outer scope and free in the inner scope. 70 | 71 | Applying this term to the named variable `V("a")` we are left with a lambda term whose free term has been instantiated to `V("a")`. 72 | 73 | scala> val constA = whnf(const(V("a"))) 74 | p: Exp[String] = Lam(Scope(V(\/-(V(a))))) 75 | 76 | Applying that to a second term gives us the term we bound to the variable: 77 | 78 | scala> val a = whnf(constA(V("b"))) 79 | a: Exp[String] = V(a) 80 | -------------------------------------------------------------------------------- /project/build.scala: -------------------------------------------------------------------------------- 1 | import sbt._ 2 | import Project.Setting 3 | import Keys._ 4 | 5 | object build extends Build { 6 | 7 | type Sett = Project.Setting[_] 8 | 9 | lazy val standardSettings = Defaults.defaultSettings ++ Seq[Sett]( 10 | organization := "bound", 11 | version := "1.3.0", 12 | resolvers += Resolver.jcenterRepo, 13 | resolvers += "Typesafe Sonatype Snapshots" at "http://repo.typesafe.com/typesafe/sonatype-snapshots/", 14 | scalaVersion := "2.11.6", 15 | description := "A Scala library for variable bindings in embedded languages.", 16 | licenses += ("MIT", url("http://opensource.org/licenses/MIT")), 17 | publishMavenStyle := true, 18 | crossScalaVersions := Seq("2.10.5", "2.11.6"), 19 | scalacOptions <++= (scalaVersion) map { sv => 20 | val versionDepOpts = 21 | if (sv startsWith "2.9") Seq() 22 | else Seq("-feature", "-language:higherKinds", "-language:implicitConversions") 23 | Seq("-deprecation", "-unchecked") ++ versionDepOpts 24 | }, 25 | libraryDependencies += "org.scalaz" %% "scalaz-core" % "7.1.+" 26 | ) 27 | 28 | lazy val bound = Project( 29 | id = "bound", 30 | base = file("."), 31 | settings = standardSettings ++ Seq[Sett]( 32 | name := "bound", 33 | publishArtifact := false 34 | ), 35 | aggregate = Seq(core, scalacheckBinding, f0Binding, tests, examples) 36 | ) 37 | 38 | lazy val core = Project( 39 | id = "core", 40 | base = file("core"), 41 | settings = standardSettings ++ Seq[Sett]( 42 | name := "bound-core", 43 | description := "A Scala library for variable bindings in embedded languages." 44 | ) 45 | ) 46 | 47 | lazy val scalacheckBinding = Project( 48 | id = "bound-scalacheck-binding", 49 | base = file("scalacheck-binding"), 50 | dependencies = Seq(core), 51 | settings = standardSettings ++ Seq[Sett]( 52 | name := "bound-scalacheck-binding", 53 | libraryDependencies += "org.scalacheck" %% "scalacheck" % "1.12.2" 54 | ) 55 | ) 56 | 57 | lazy val f0Binding = Project( 58 | id = "bound-f0-binding", 59 | base = file("f0-binding"), 60 | dependencies = Seq(core), 61 | settings = standardSettings ++ Seq[Sett]( 62 | name := "bound-f0-binding", 63 | libraryDependencies += "com.clarifi" %% "f0" % "1.1.3" 64 | ) 65 | ) 66 | 67 | lazy val tests = Project( 68 | id = "bound-tests", 69 | base = file("tests"), 70 | dependencies = Seq(core, f0Binding, scalacheckBinding % "test"), 71 | settings = standardSettings ++ Seq[Sett]( 72 | name := "bound-tests", 73 | publishArtifact := false, 74 | libraryDependencies += "org.scalacheck" %% "scalacheck" % "1.12.2" 75 | ) 76 | ) 77 | 78 | lazy val examples = Project( 79 | id = "bound-examples", 80 | base = file("examples"), 81 | dependencies = Seq(core, scalacheckBinding), 82 | settings = standardSettings ++ Seq[Sett]( 83 | name := "bound-examples", 84 | publishArtifact := false, 85 | libraryDependencies += "com.clarifi" %% "f0" % "1.1.3" 86 | ) 87 | ) 88 | } 89 | -------------------------------------------------------------------------------- /core/src/main/scala/bound/package.scala: -------------------------------------------------------------------------------- 1 | import scalaz._ 2 | import Scalaz._ 3 | 4 | package object bound { 5 | type Var[A,B] = A \/ B 6 | 7 | implicit def varShow[A:Show,B:Show]: Show[Var[A,B]] = new Show[Var[A,B]] { 8 | override def shows(v: Var[A,B]) = v match { 9 | case B(a) => "B(%s)".format(a.shows) 10 | case F(a) => "F(%s)".format(a.shows) 11 | } 12 | } 13 | 14 | implicit def varEqual1[A:Equal]: Equal1[({type λ[α] = Var[A,α]})#λ] = 15 | new Equal1[({type λ[α] = Var[A,α]})#λ] { 16 | def equal[B](a1: Var[A,B], a2: Var[A,B])(implicit a: Equal[B]): Boolean = 17 | implicitly[Equal[Var[A,B]]].equal(a1, a2) 18 | } 19 | 20 | import Scope._ 21 | 22 | /** 23 | * Replace the free variable `a` with `p` in `w`. 24 | * 25 | * {{{ 26 | * scala> substitute("foo", List("qux", "corge"), List("bar", "foo", "baz")) 27 | * res0: List[String] = List(bar, qux, corge, baz) 28 | * }}} 29 | */ 30 | def substitute[F[_]:Monad,A:Equal](a: A, p: F[A], w: F[A]): F[A] = 31 | w flatMap (b => if (a === b) p else Monad[F].pure(b)) 32 | 33 | /** 34 | * Replace a free variable `a` with another free variable `b` in `w`. 35 | * 36 | * {{{ 37 | * scala> substituteVar("Alice", "Donna", List("Alice", "Bob", "Charlie") 38 | * List[String] = List(Donna, Bob, Charlie) 39 | * res0: List[String] = List(bar, qux, corge, baz) 40 | * }}} 41 | */ 42 | def substituteVar[F[_]:Functor,A:Equal](a: A, p: A, w: F[A]): F[A] = 43 | w map (b => if (a === b) p else b) 44 | 45 | /** 46 | * If a term has no free variables, you can freely change the type of free variables it's 47 | * parameterized on. 48 | */ 49 | def closed[F[_]:Traverse,A,B](w: F[A]): Option[F[B]] = 50 | w traverse (_ => None) 51 | 52 | /** A closed term has no free variables. */ 53 | def isClosed[F[_]:Foldable,A](w: F[A]): Boolean = 54 | w.all(_ => false) 55 | 56 | /** 57 | * Capture some free variables in an expression to yield a scope with bound variables in `b`. 58 | * 59 | * {{{ 60 | * scala> abstrakt("aeiou".toList)(x => Some("bario".indexOf(x)).filter(_ >= 0)).shows 61 | * res0: String = Scope([B(1),F([e]),B(3),B(4),F([u])]) 62 | * }}} 63 | */ 64 | def abstrakt[F[_]:Monad,A,B](w: F[A])(f: A => Option[B]): Scope[B,F,A] = 65 | Scope(w map (a => f(a) match { 66 | case Some(z) => B(z) 67 | case None => F(Monad[F].pure(a)) 68 | })) 69 | 70 | /** 71 | * Abstract over a single variable. 72 | * 73 | * {{{ 74 | * scala> abstract1('a', "abracadabra".toList).shows 75 | * res0: String = Scope([B(()),F([b]),F([r]),B(()),F([c]),B(()),F([d]),B(()),F([b]),F([r]),B(())]) 76 | * }}} 77 | */ 78 | def abstract1[F[_]:Monad,A:Equal,B](a: A, w: F[A]): Scope[Unit,F,A] = 79 | abstrakt(w)(b => if (a === b) Some(()) else None) 80 | 81 | /** Abstraction capturing named bound variables. */ 82 | def abstractName[F[_]:Monad,A,B](w: F[A])(f: A => Option[B]): Scope[Name[A,B],F,A] = 83 | Scope[Name[A,B],F,A](w map (a => f(a) match { 84 | case Some(b) => B(Name(a, b)) 85 | case None => F(Monad[F].pure(a)) 86 | })) 87 | 88 | /** Abstract over a single variable */ 89 | def abstract1Name[F[_]:Monad,A:Equal](a: A, t: F[A]): Scope[Name[A,Unit],F,A] = 90 | abstractName(t)(b => if (a === b) Some(()) else None) 91 | 92 | /** 93 | * Enter a scope, instantiating all bound variables, but discarding (comonadic) 94 | * metadata, like its name. 95 | **/ 96 | def instantiateName[F[_]:Monad,N[_]:Comonad,A,B](e: Scope[N[B],F,A])(k: B => F[A]): F[A] = 97 | e.unscope flatMap { 98 | case B(b) => k(Comonad[N].copoint(b)) 99 | case F(a) => a 100 | } 101 | 102 | /** 103 | * Enter a scope, instantiating all bound variables. 104 | * 105 | * {{{ 106 | * scala> instantiate(abstract1('a', "abracadabra".toList))(_ => "foo".toList).mkString 107 | * res0: String = foobrfoocfoodfoobrfoo 108 | * }}} 109 | */ 110 | def instantiate[F[_]:Monad,A,B](e: Scope[B,F,A])(k: B => F[A]): F[A] = e instantiate k 111 | 112 | /** Enter a scope that binds one variable, instantiating it. */ 113 | def instantiate1[F[_]:Monad,N,A](e: F[A], s: Scope[N, F, A]): F[A] = s instantiate1 e 114 | 115 | /** 116 | * Quotients out the possible placements of `F` in `Scope` by distributing them all 117 | * to the leaves. This yields a more traditional de Bruijn indexing scheme for bound 118 | * variables. 119 | */ 120 | def fromScope[F[_]:Monad,A,B](s: Scope[B, F, A]): F[Var[B, A]] = 121 | s.unscope flatMap { 122 | case F(e) => e.map(F(_)) 123 | case B(b) => Monad[F].pure(B(b)) 124 | } 125 | 126 | /** Convert from traditional de Bruijn to generalized de Bruijn indices. */ 127 | def toScope[F[_]:Monad,A,B](e: F[Var[B, A]]): Scope[B, F, A] = 128 | Scope[B, F, A](e map (_ map (Monad[F].pure(_)))) 129 | 130 | implicit def toBoundOps[B,F[_],A](s: Scope[B,F,A]) = 131 | new BoundOps[({type λ[φ[_],α] = Scope[B,φ,α]})#λ,F,A] { 132 | def self = s 133 | implicit def T = scopeBound 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /core/src/main/scala/bound/Scope.scala: -------------------------------------------------------------------------------- 1 | package bound 2 | 3 | import scalaz._ 4 | import Scalaz._ 5 | 6 | /** 7 | * A value of type `Scope[B,F,A]` is an `F` expression with bound variables in `B` 8 | * and free variables in `A`. 9 | */ 10 | abstract class Scope[B,F[_],A] { 11 | def unscope: F[Var[B,F[A]]] 12 | 13 | def map[C](f: A => C)(implicit M: Functor[F]): Scope[B,F,C] = 14 | Scope(unscope map (_ map (_ map f))) 15 | 16 | def flatMap[C,D >: B](f: A => Scope[D,F,C])(implicit M: Monad[F]): Scope[D,F,C] = 17 | Scope(unscope flatMap { 18 | case B(b) => M.pure(B(b)) 19 | case F(a) => a flatMap (v => f(v).unscope) 20 | }) 21 | 22 | def traverse[M[_],C](f: A => M[C])(implicit M: Applicative[M], T: Traverse[F]): M[Scope[B,F,C]] = { 23 | val ertraverse = Traverse[({type λ[α] = Var[B, α]})#λ] // \/#traverse broken until scalaz 7.1 24 | unscope traverse (ertraverse.traverse(_)(_ traverse f)) map (Scope(_)) 25 | } 26 | 27 | def foldMap[M](f: A => M)(implicit F: Foldable[F], M: Monoid[M]): M = 28 | unscope foldMap (_ foldMap (_ foldMap f)) 29 | 30 | def foldRight[M](m: M)(f: (A, => M) => M)(implicit F: Foldable[F]) = 31 | unscope.foldRight(m)((v, b) => v.foldRight(b)((fa, bz) => fa.foldRight(bz)(f))) 32 | 33 | /** Bind a variable in a scope. */ 34 | def bind[C](f: A => F[C])(implicit M: Monad[F]): Scope[B,F,C] = 35 | Scope[B,F,C](unscope map (_.map(_ flatMap f))) 36 | 37 | /** 38 | * Enter a scope, instantiating all bound variables. 39 | * 40 | * {{{ 41 | * scala> abstract1('a', "abracadabra".toList).instantiate(_ => "foo".toList).mkString 42 | * res0: String = foobrfoocfoodfoobrfoo 43 | * }}} 44 | */ 45 | def instantiate(k: B => F[A])(implicit M: Monad[F]): F[A] = 46 | unscope flatMap { 47 | case B(b) => k(b) 48 | case F(a) => a 49 | } 50 | 51 | /** Enter a scope that binds one variable, instantiating it. */ 52 | def instantiate1(e: F[A])(implicit M: Monad[F]): F[A] = 53 | instantiate(_ => e) 54 | 55 | /** 56 | * Quotients out the possible placements of `F` in this `Scope` by distributing them all 57 | * to the leaves. This yields a more traditional de Bruijn indexing scheme for bound 58 | * variables. 59 | */ 60 | def toDeBruijn(implicit M: Monad[F]): F[Var[B, A]] = 61 | unscope flatMap { 62 | case F(e) => e.map(F(_)) 63 | case B(b) => M.pure(B(b)) 64 | } 65 | 66 | /** Simultaneously substitute bound and free variables. */ 67 | def splat[C](fb: B => F[C], fv: A => F[C])(implicit M: Monad[F]): F[C] = 68 | unscope flatMap { 69 | case -\/(b) => fb(b) 70 | case \/-(tm) => tm flatMap fv 71 | } 72 | 73 | import Show._ 74 | override def toString = Scope.scopeShow[Any,Any,Any](showA, showA, showA, showA).shows(this.asInstanceOf[Scope[Any,Any,Any]]) 75 | } 76 | 77 | sealed abstract class ScopeInstances0 { 78 | implicit def scopeFunctor[F[_]:Functor,D]: Functor[({type λ[α] = Scope[D,F,α]})#λ] = 79 | new Functor[({type λ[α] = Scope[D,F,α]})#λ] { 80 | val M = Functor[F] 81 | override def map[A,B](a: Scope[D,F,A])(f: A => B) = a map f 82 | } 83 | } 84 | 85 | sealed abstract class ScopeInstances extends ScopeInstances0 { 86 | implicit def scopeMonad[F[_]:Monad,D]: Monad[({type λ[α] = Scope[D,F,α]})#λ] = 87 | new Monad[({type λ[α] = Scope[D,F,α]})#λ] { 88 | val M = Monad[F] 89 | override def map[A,B](a: Scope[D,F,A])(f: A => B) = a map f 90 | def point[A](a: => A) = Scope(M.pure(F(M.pure(a)))) 91 | def bind[A,B](e: Scope[D,F,A])(f: A => Scope[D,F,B]) = e flatMap f 92 | } 93 | 94 | implicit def scopeFoldable[F[_]:Foldable,D]: Foldable[({type λ[α] = Scope[D,F,α]})#λ] = 95 | new Foldable[({type λ[α] = Scope[D,F,α]})#λ] { 96 | val T = Foldable[F] 97 | override def foldMap[A,M:Monoid](a: Scope[D,F,A])(f: A => M) = a foldMap f 98 | def foldRight[A,B](a: Scope[D,F,A], z: => B)(f: (A, => B) => B) = a.foldRight(z)(f) 99 | } 100 | } 101 | 102 | object Scope extends ScopeInstances { 103 | def apply[B,F[_],A](f: F[Var[B,F[A]]]): Scope[B,F,A] = new Scope[B,F,A] { 104 | def unscope = f 105 | } 106 | 107 | implicit def scopeShow[B,F[_],A](implicit B: Show[B], 108 | F: Show[F[Var[B,F[A]]]], 109 | A: Show[A], 110 | FA: Show[F[A]]): Show[Scope[B,F,A]] = 111 | new Show[Scope[B,F,A]] { 112 | override def shows(s: Scope[B,F,A]) = "Scope(%s)".format(s.unscope.shows) 113 | } 114 | 115 | implicit def scopeEqual[B,F[_],A](implicit EB: Equal[B], M: Monad[F], EF: Equal1[F], EA: Equal[A]): Equal[Scope[B,F,A]] = 116 | new Equal[Scope[B,F,A]] { 117 | def equal(a: Scope[B,F,A], b: Scope[B,F,A]): Boolean = scopeEqual1[B,F].equal(a, b) 118 | } 119 | 120 | implicit def scopeEqual1[B,F[_]](implicit EB: Equal[B], M: Monad[F], E1F: Equal1[F]): Equal1[({type λ[α] = Scope[B,F,α]})#λ] = 121 | new Equal1[({type λ[α] = Scope[B,F,α]})#λ] { 122 | def equal[A](a: Scope[B,F,A], b: Scope[B,F,A])(implicit EA: Equal[A]): Boolean = 123 | E1F.equal(fromScope(a), fromScope(b)) 124 | } 125 | 126 | implicit def scopeTraverse[F[_]:Traverse,D]: Traverse[({type λ[α] = Scope[D,F,α]})#λ] = 127 | new Traverse[({type λ[α] = Scope[D,F,α]})#λ] { 128 | def traverseImpl[M[_]:Applicative,A,B](a: Scope[D,F,A])(f: A => M[B]) = a traverse f 129 | } 130 | 131 | implicit def scopeMonadTrans[B]: MonadTrans[({type λ[φ[_],α] = Scope[B,φ,α]})#λ] = 132 | new MonadTrans[({type λ[φ[_],α] = Scope[B,φ,α]})#λ] { 133 | def liftM[M[_]:Monad,A](m: M[A]) = Scope[B,M,A](Monad[M].point(F(m))) 134 | def apply[M[_]:Monad]: Monad[({type λ[α] = Scope[B,M,α]})#λ] = scopeMonad[M,B] 135 | } 136 | 137 | implicit def scopeBound[B]: Bound[({type λ[φ[_],α] = Scope[B,φ,α]})#λ] = 138 | new Bound[({type λ[φ[_],α] = Scope[B,φ,α]})#λ] { 139 | def bind[F[_]:Monad,A,C](m: Scope[B,F,A])(f: A => F[C]): Scope[B,F,C] = m bind f 140 | } 141 | } 142 | 143 | -------------------------------------------------------------------------------- /examples/src/main/scala/exp/Exp.scala: -------------------------------------------------------------------------------- 1 | package exp 2 | 3 | import bound._ 4 | import bound.Scope._ 5 | import scalaz._ 6 | import Scalaz._ 7 | 8 | /** 9 | data Exp a 10 | = V a 11 | | Exp a :@ Exp a 12 | | Lam (Scope () Exp a) 13 | | Let [Scope Int Exp a] (Scope Int Exp a) 14 | deriving (Eq,Ord,Show,Read) 15 | 16 | -- | Reduce a term to weak head normal form 17 | whnf :: Exp a -> Exp a 18 | whnf e@V{} = e 19 | whnf e@Lam{} = e 20 | whnf (f :@ a) = case whnf f of 21 | Lam b -> whnf (instantiate1 a b) 22 | f' -> f' :@ a 23 | whnf (Let bs b) = whnf (inst b) 24 | where es = map inst bs 25 | inst = instantiate (es !!) 26 | */ 27 | 28 | //https://github.com/ekmett/bound/blob/master/examples/Simple.hs 29 | object Exp { 30 | 31 | sealed trait Exp[A] { 32 | def *(e:Exp[A]) = App(this, e) 33 | } 34 | 35 | object V { 36 | def apply[A](a: => A): Exp[A] = new V(a) 37 | def unapply[A](e: Exp[A]): Option[A] = e match { 38 | case v:V[A] => Some(v.get) 39 | case _ => None 40 | } 41 | } 42 | class V[A](a: => A) extends Exp[A]{ 43 | lazy val get = a 44 | override def toString = s"V($a)" 45 | } 46 | 47 | object Lam{ 48 | def apply[A](s: => Scope[Unit, Exp, A]): Exp[A] = new Lam(s) 49 | def unapply[A](e: Exp[A]): Option[Scope[Unit, Exp, A]] = e match { 50 | case l:Lam[A] => Some(l.get) 51 | case _ => None 52 | } 53 | } 54 | class Lam[A](s: => Scope[Unit, Exp, A]) extends Exp[A] { 55 | lazy val get = s 56 | override def toString = s"Lam($s)" 57 | } 58 | 59 | object App { 60 | def apply[A](f: => Exp[A], x: => Exp[A]): Exp[A] = new App(f, x) 61 | def unapply[A](e: Exp[A]): Option[(Exp[A], Exp[A])] = e match { 62 | case a:App[A] => Some(a.get) 63 | case _ => None 64 | } 65 | } 66 | class App[A](f: => Exp[A], x: => Exp[A]) extends Exp[A] { 67 | lazy val get = (f, x) 68 | override def toString = s"App($f, $x)" 69 | } 70 | 71 | object Let { 72 | def apply[A](bindings: => List[Scope[Int, Exp, A]], body: => Scope[Int, Exp, A]): Exp[A] = new Let(bindings, body) 73 | def unapply[A](e: Exp[A]): Option[(List[Scope[Int, Exp, A]], Scope[Int, Exp, A])] = e match { 74 | case l:Let[A] => Some(l.get) 75 | case _ => None 76 | } 77 | } 78 | class Let[A](bindings: => List[Scope[Int, Exp, A]], body: => Scope[Int, Exp, A]) extends Exp[A]{ 79 | lazy val get = (bindings, body) 80 | override def toString = s"Let($bindings, $body)" 81 | } 82 | 83 | implicit def expMonad: Monad[Exp] = new Monad[Exp]{ 84 | def point[A](a: => A) = V(a) 85 | def bind[A,B](e: Exp[A])(f: A => Exp[B]): Exp[B] = e match { 86 | case V(a) => f(a) 87 | case Lam(s) => Lam(s >>>= f) 88 | case App(fun, arg) => App(fun >>= f, arg >>= f) 89 | case Let(bs, b) => Let(bs map (_ >>>= f), b >>>= f) 90 | } 91 | } 92 | 93 | implicit def expTraversable: Traverse[Exp] = new Traverse[Exp]{ 94 | def traverseImpl[F[_], A, B](exp : Exp[A])(f : A => F[B])(implicit A: Applicative[F]) : F[Exp[B]] = exp match { 95 | case V(a) => f(a).map(V(_)) 96 | case App(x, y) => A.apply2(traverse(x)(f), traverse(y)(f))(App(_, _)) 97 | case Lam(e) => e.traverse(f).map(Lam(_)) 98 | case Let(bs, b) => A.apply2(bs.traverse(s => s.traverse(f)), b.traverse(f))(Let(_, _)) 99 | } 100 | } 101 | 102 | def instantiateR[B,F[_],A](f: B => F[A])(s: Scope[B,F,A])(implicit M: Monad[F]): F[A] = 103 | instantiate(s)(f) 104 | 105 | def abstractR[B,F[_],A](f : A => Option[B])(w : F[A])(implicit M: scalaz.Monad[F]) = abstrakt(w)(f) 106 | 107 | def nf[A](e:Exp[A]): Exp[A] = e match { 108 | case V(_) => e 109 | case Lam(b) => Lam(toScope(nf(fromScope(b)))) 110 | case App(f, a) => whnf(f) match { 111 | case Lam(b) => nf(instantiate1(a, b)) 112 | case f1 => App(nf(f), nf(a)) 113 | } 114 | case Let(bs, b) => 115 | def inst = instantiateR((i: Int) => es(i)) _ // Scope[Int,Exp,A] => Exp[A] 116 | lazy val es: Stream[Exp[A]] = bs.toStream.map(inst) 117 | nf(inst(b)) 118 | } 119 | 120 | def whnf[A](e: Exp[A]): Exp[A] = e match { 121 | case V(_) => e 122 | case Lam(_) => e 123 | case App(f, a) => whnf(f) match { 124 | case Lam(b) => whnf(instantiate1(a, b)) 125 | case _ => App(f, a) 126 | } 127 | case Let(bs, b) => 128 | def inst = instantiateR((i: Int) => es(i)) _ // Scope[Int,Exp,A] => Exp[A] 129 | def es: Stream[Exp[A]] = bs.toStream.map(inst) 130 | whnf(inst(b)) 131 | } 132 | 133 | // A smart constructor for Lam 134 | // >>> lam "y" (lam "x" (V "x" :@ V "y")) 135 | // Lam (Scope (Lam (Scope (V (B ()) :@ V (F (V (B ()))))))) 136 | def lam[A](v: A, b: Exp[A])(implicit e: Equal[A]) = Lam(abstract1(v,b)) 137 | 138 | def let_[A](es: List[(A, Exp[A])], e:Exp[A]): Exp[A] = es match { 139 | case Nil => e 140 | case _ => 141 | def abstr(e:Exp[A]) = abstractR((a:A) => { 142 | val i = es.map(_._1).indexOf(a) 143 | if(i>=0) Some(i) else None 144 | })(e) 145 | Let(es.map(t => abstr(t._2)), abstr(e)) 146 | } 147 | 148 | implicit class PimpedExp(e: Exp[String]) { 149 | def !:(s:String) = lam(s, e) 150 | } 151 | 152 | def closed[F[_], A, B](fa:F[A])(implicit T: Traverse[F]): Option[F[B]] = 153 | fa.traverse(Function.const(None)) 154 | 155 | // true :: Exp String 156 | // true = lam "F" $ lam "T" $ V "T" 157 | val True: Exp[String] = lam("F", lam("T", V("T"))) 158 | 159 | val cooked = closed[Exp, String, String](let_(List( 160 | ("False", "f" !: "t" !: V("f")) 161 | , ("True", "f" !: "t" !: V("t")) 162 | , ("if", "b" !: "t" !: "f" !: V("b") * V("f") * V("t")) 163 | , ("Zero", "z" !: "s" !: V("z")) 164 | , ("Succ", "n" !: "z" !: "s" !: V("s") * V("n")) 165 | , ("one", V("Succ") * V("Zero")) 166 | , ("two", V("Succ") * V("one")) 167 | , ("three", V("Succ") * V("two")) 168 | , ("isZero", "n" !: V("n") * V("True") * ("m" !: V("False"))) 169 | , ("const", "x" !: "y" !: V("x")) 170 | , ("Pair", "a" !: "b" !: "p" !: V("p") * V("a") * V("b")) 171 | , ("fst", "ab" !: V("ab") * ("a" !: "b" !: V("a"))) 172 | , ("snd", "ab" !: V("ab") * ("a" !: "b" !: V("b"))) 173 | , ("add", "x" !: "y" !: V("x") * V("y") * ("n" !: V("Succ") * (V("add") * V("n") * V("y")))) 174 | , ("mul", "x" !: "y" !: V("x") * V("Zero") * ("n" !: V("add") * V("y") * (V("mul") * V("n") * V("y")))) 175 | , ("fac", "x" !: V("x") * V("one") * ("n" !: V("mul") * V("x") * (V("fac") * V("n")))) 176 | , ("eqnat", "x" !: "y" !: V("x") * (V("y") * V("True") * (V("const") * V("False"))) * ("x1" !: V("y") * V("False") * ("y1" !: V("eqnat") * V("x1") * V("y1")))) 177 | , ("sumto", "x" !: V("x") * V("Zero") * ("n" !: V("add") * V("x") * (V("sumto") * V("n")))) 178 | , ("n5", V("add") * V("two") * V("three")) 179 | , ("n6", V("add") * V("three") * V("three")) 180 | , ("n17", V("add") * V("n6") * (V("add") * V("n6") * V("n5"))) 181 | , ("n37", V("Succ") * (V("mul") * V("n6") * V("n6"))) 182 | , ("n703", V("sumto") * V("n37")) 183 | , ("n720", V("fac") * V("n6")) 184 | ), (V("eqnat") * V("n720") * (V("add") * V("n703") * V("n17"))))).get 185 | 186 | 187 | def main(args: Array[String]){ 188 | println(nf(cooked)) 189 | } 190 | 191 | } 192 | 193 | 194 | // val x: Scope[Unit, Exp, Var[Unit, Exp[Nothing]]] = Scope(App(V (B ()), V (F (V (B ()))))) 195 | // val y: Scope[Unit, Exp, Nothing] = Scope(V (B ())) 196 | // val z = Lam(Scope(Lam(y))) 197 | -------------------------------------------------------------------------------- /sbt: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # A more capable sbt runner, coincidentally also called sbt. 4 | # Author: Paul Phillips 5 | 6 | # todo - make this dynamic 7 | declare -r sbt_release_version=0.13.8 8 | declare -r sbt_snapshot_version=0.13.9-SNAPSHOT 9 | 10 | unset sbt_jar sbt_dir sbt_create sbt_snapshot sbt_launch_dir 11 | unset scala_version java_home sbt_explicit_version 12 | unset verbose debug quiet 13 | 14 | build_props_sbt () { 15 | if [[ -f project/build.properties ]]; then 16 | versionLine=$(grep ^sbt.version project/build.properties) 17 | versionString=${versionLine##sbt.version=} 18 | echo "$versionString" 19 | fi 20 | } 21 | 22 | update_build_props_sbt () { 23 | local ver="$1" 24 | local old=$(build_props_sbt) 25 | 26 | if [[ $ver == $old ]]; then 27 | return 28 | elif [[ -f project/build.properties ]]; then 29 | perl -pi -e "s/^sbt\.version=.*\$/sbt.version=${ver}/" project/build.properties 30 | grep -q '^sbt.version=' project/build.properties || echo "sbt.version=${ver}" >> project/build.properties 31 | 32 | echo !!! 33 | echo !!! Updated file project/build.properties setting sbt.version to: $ver 34 | echo !!! Previous value was: $old 35 | echo !!! 36 | fi 37 | } 38 | 39 | sbt_version () { 40 | if [[ -n $sbt_explicit_version ]]; then 41 | echo $sbt_explicit_version 42 | else 43 | local v=$(build_props_sbt) 44 | if [[ -n $v ]]; then 45 | echo $v 46 | else 47 | echo $sbt_release_version 48 | fi 49 | fi 50 | } 51 | 52 | echoerr () { 53 | echo 1>&2 "$@" 54 | } 55 | vlog () { 56 | [[ $verbose || $debug ]] && echoerr "$@" 57 | } 58 | dlog () { 59 | [[ $debug ]] && echoerr "$@" 60 | } 61 | 62 | # this seems to cover the bases on OSX, and someone will 63 | # have to tell me about the others. 64 | get_script_path () { 65 | local path="$1" 66 | [[ -L "$path" ]] || { echo "$path" ; return; } 67 | 68 | local target=$(readlink "$path") 69 | if [[ "${target:0:1}" == "/" ]]; then 70 | echo "$target" 71 | else 72 | echo "$(dirname $path)/$target" 73 | fi 74 | } 75 | 76 | # a ham-fisted attempt to move some memory settings in concert 77 | # so they need not be dicked around with individually. 78 | get_mem_opts () { 79 | local mem=${1:-1536} 80 | local perm=$(( $mem / 4 )) 81 | (( $perm > 256 )) || perm=256 82 | (( $perm < 1024 )) || perm=1024 83 | local codecache=$(( $perm / 2 )) 84 | 85 | echo "-Xms${mem}m -Xmx${mem}m -XX:MaxPermSize=${perm}m -XX:ReservedCodeCacheSize=${codecache}m" 86 | } 87 | 88 | die() { 89 | echo "Aborting: $@" 90 | exit 1 91 | } 92 | 93 | make_url () { 94 | groupid="$1" 95 | category="$2" 96 | version="$3" 97 | 98 | echo "http://typesafe.artifactoryonline.com/typesafe/ivy-$category/$groupid/sbt-launch/$version/sbt-launch.jar" 99 | } 100 | 101 | declare -r default_jvm_opts="-Dfile.encoding=UTF8" 102 | declare -r default_sbt_opts="-XX:+CMSClassUnloadingEnabled" 103 | declare -r default_sbt_mem=1536 104 | declare -r noshare_opts="-Dsbt.global.base=project/.sbtboot -Dsbt.boot.directory=project/.boot -Dsbt.ivy.home=project/.ivy" 105 | declare -r sbt_opts_file=".sbtopts" 106 | declare -r jvm_opts_file=".jvmopts" 107 | declare -r latest_28="2.8.2" 108 | declare -r latest_29="2.9.1" 109 | declare -r latest_210="2.10.0-SNAPSHOT" 110 | 111 | declare -r script_path=$(get_script_path "$BASH_SOURCE") 112 | declare -r script_dir="$(dirname $script_path)" 113 | declare -r script_name="$(basename $script_path)" 114 | 115 | # some non-read-onlies set with defaults 116 | declare java_cmd=java 117 | declare sbt_launch_dir="$script_dir/.lib" 118 | declare sbt_mem=$default_sbt_mem 119 | 120 | # pull -J and -D options to give to java. 121 | declare -a residual_args 122 | declare -a java_args 123 | declare -a scalac_args 124 | declare -a sbt_commands 125 | 126 | build_props_scala () { 127 | if [[ -f project/build.properties ]]; then 128 | versionLine=$(grep ^build.scala.versions project/build.properties) 129 | versionString=${versionLine##build.scala.versions=} 130 | echo ${versionString%% .*} 131 | fi 132 | } 133 | 134 | execRunner () { 135 | # print the arguments one to a line, quoting any containing spaces 136 | [[ $verbose || $debug ]] && echo "# Executing command line:" && { 137 | for arg; do 138 | if printf "%s\n" "$arg" | grep -q ' '; then 139 | printf "\"%s\"\n" "$arg" 140 | else 141 | printf "%s\n" "$arg" 142 | fi 143 | done 144 | echo "" 145 | } 146 | 147 | exec "$@" 148 | } 149 | 150 | sbt_groupid () { 151 | case $(sbt_version) in 152 | 0.7.*) echo org.scala-tools.sbt ;; 153 | 0.10.*) echo org.scala-tools.sbt ;; 154 | 0.11.[12]) echo org.scala-tools.sbt ;; 155 | *) echo org.scala-sbt ;; 156 | esac 157 | } 158 | 159 | sbt_artifactory_list () { 160 | local version0=$(sbt_version) 161 | local version=${version0%-SNAPSHOT} 162 | local url="http://typesafe.artifactoryonline.com/typesafe/ivy-snapshots/$(sbt_groupid)/sbt-launch/" 163 | dlog "Looking for snapshot list at: $url " 164 | 165 | curl -s --list-only "$url" | \ 166 | grep -F $version | \ 167 | perl -e 'print reverse <>' | \ 168 | perl -pe 's#^/dev/null 182 | dlog "curl returned: $?" 183 | echo "$url" 184 | return 185 | done 186 | } 187 | 188 | jar_url () { 189 | case $(sbt_version) in 190 | 0.7.*) echo "http://simple-build-tool.googlecode.com/files/sbt-launch-0.7.7.jar" ;; 191 | *-SNAPSHOT) make_snapshot_url ;; 192 | *) make_release_url ;; 193 | esac 194 | } 195 | 196 | jar_file () { 197 | echo "$sbt_launch_dir/$1/sbt-launch.jar" 198 | } 199 | 200 | download_url () { 201 | local url="$1" 202 | local jar="$2" 203 | 204 | echo "Downloading sbt launcher $(sbt_version):" 205 | echo " From $url" 206 | echo " To $jar" 207 | 208 | mkdir -p $(dirname "$jar") && { 209 | if which curl >/dev/null; then 210 | curl --fail --silent "$url" --output "$jar" 211 | elif which wget >/dev/null; then 212 | wget --quiet -O "$jar" "$url" 213 | fi 214 | } && [[ -f "$jar" ]] 215 | } 216 | 217 | acquire_sbt_jar () { 218 | sbt_url="$(jar_url)" 219 | sbt_jar="$(jar_file $(sbt_version))" 220 | 221 | [[ -f "$sbt_jar" ]] || download_url "$sbt_url" "$sbt_jar" 222 | } 223 | 224 | usage () { 225 | cat < path to global settings/plugins directory (default: ~/.sbt/) 235 | -sbt-boot path to shared boot directory (default: ~/.sbt/boot in 0.11 series) 236 | -ivy path to local Ivy repository (default: ~/.ivy2) 237 | -mem set memory options (default: $sbt_mem, which is 238 | $(get_mem_opts $sbt_mem) ) 239 | -no-share use all local caches; no sharing 240 | -offline put sbt in offline mode 241 | -jvm-debug Turn on JVM debugging, open at the given port. 242 | -batch Disable interactive mode 243 | 244 | # sbt version (default: from project/build.properties if present, else latest release) 245 | !!! The only way to accomplish this pre-0.12.0 if there is a build.properties file which 246 | !!! contains an sbt.version property is to update the file on disk. That's what this does. 247 | -sbt-version use the specified version of sbt 248 | -sbt-jar use the specified jar as the sbt launcher 249 | -sbt-snapshot use a snapshot version of sbt 250 | -sbt-launch-dir directory to hold sbt launchers (default: $sbt_launch_dir) 251 | 252 | # scala version (default: as chosen by sbt) 253 | -28 use $latest_28 254 | -29 use $latest_29 255 | -210 use $latest_210 256 | -scala-home use the scala build at the specified directory 257 | -scala-version use the specified version of scala 258 | 259 | # java version (default: java from PATH, currently $(java -version |& grep version)) 260 | -java-home alternate JAVA_HOME 261 | 262 | # jvm options and output control 263 | JAVA_OPTS environment variable holding jvm args, if unset uses "$default_jvm_opts" 264 | SBT_OPTS environment variable holding jvm args, if unset uses "$default_sbt_opts" 265 | .jvmopts if file is in sbt root, it is prepended to the args given to the jvm 266 | .sbtopts if file is in sbt root, it is prepended to the args given to **sbt** 267 | -Dkey=val pass -Dkey=val directly to the jvm 268 | -J-X pass option -X directly to the jvm (-J is stripped) 269 | -S-X add -X to sbt's scalacOptions (-J is stripped) 270 | 271 | In the case of duplicated or conflicting options, the order above 272 | shows precedence: JAVA_OPTS lowest, command line options highest. 273 | EOM 274 | } 275 | 276 | addJava () { 277 | dlog "[addJava] arg = '$1'" 278 | java_args=( "${java_args[@]}" "$1" ) 279 | } 280 | addSbt () { 281 | dlog "[addSbt] arg = '$1'" 282 | sbt_commands=( "${sbt_commands[@]}" "$1" ) 283 | } 284 | addScalac () { 285 | dlog "[addScalac] arg = '$1'" 286 | scalac_args=( "${scalac_args[@]}" "$1" ) 287 | } 288 | addResidual () { 289 | dlog "[residual] arg = '$1'" 290 | residual_args=( "${residual_args[@]}" "$1" ) 291 | } 292 | addResolver () { 293 | addSbt "set resolvers in ThisBuild += $1" 294 | } 295 | addDebugger () { 296 | addJava "-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=$1" 297 | } 298 | get_jvm_opts () { 299 | # echo "${JAVA_OPTS:-$default_jvm_opts}" 300 | # echo "${SBT_OPTS:-$default_sbt_opts}" 301 | 302 | [[ -f "$jvm_opts_file" ]] && cat "$jvm_opts_file" 303 | } 304 | 305 | process_args () 306 | { 307 | require_arg () { 308 | local type="$1" 309 | local opt="$2" 310 | local arg="$3" 311 | 312 | if [[ -z "$arg" ]] || [[ "${arg:0:1}" == "-" ]]; then 313 | die "$opt requires <$type> argument" 314 | fi 315 | } 316 | while [[ $# -gt 0 ]]; do 317 | case "$1" in 318 | -h|-help) usage; exit 1 ;; 319 | -v|-verbose) verbose=1 && shift ;; 320 | -d|-debug) debug=1 && shift ;; 321 | -q|-quiet) quiet=1 && shift ;; 322 | 323 | -ivy) require_arg path "$1" "$2" && addJava "-Dsbt.ivy.home=$2" && shift 2 ;; 324 | -mem) require_arg integer "$1" "$2" && sbt_mem="$2" && shift 2 ;; 325 | -no-colors) addJava "-Dsbt.log.noformat=true" && shift ;; 326 | -no-share) addJava "$noshare_opts" && shift ;; 327 | -sbt-boot) require_arg path "$1" "$2" && addJava "-Dsbt.boot.directory=$2" && shift 2 ;; 328 | -sbt-dir) require_arg path "$1" "$2" && sbt_dir="$2" && shift 2 ;; 329 | -debug-inc) addJava "-Dxsbt.inc.debug=true" && shift ;; 330 | -offline) addSbt "set offline := true" && shift ;; 331 | -jvm-debug) require_arg port "$1" "$2" && addDebugger $2 && shift 2 ;; 332 | -batch) exec 0 )) || echo "Starting $script_name: invoke with -help for other options" 394 | 395 | # verify this is an sbt dir or -create was given 396 | [[ -f ./build.sbt || -d ./project || -n "$sbt_create" ]] || { 397 | cat <