├── .travis.yml ├── README.md ├── LICENSE ├── src ├── main │ └── scala │ │ └── sext │ │ ├── OrderedMap.scala │ │ ├── Cli.scala │ │ ├── Benchmarking.scala │ │ └── package.scala └── test │ └── scala │ └── sext │ ├── OrderedMapTest.scala │ ├── Demo.scala │ └── Test.scala └── pom.xml /.travis.yml: -------------------------------------------------------------------------------- 1 | language: scala 2 | 3 | jdk: 4 | - oraclejdk8 5 | 6 | scala: 7 | - 2.12.0 8 | 9 | install: 10 | mvn install -DskipTests=true -Dgpg.skip=true 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #SExt 2 | A small library that adds the missing useful functions to the standard Scala library. These include 3 | 4 | * `unfold`, `unfold1`, `foldTo`, `foldFrom` 5 | * `mapKeys` 6 | * `zipBy`, `unzip4` 7 | * `treeString`,`valueTreeString` 8 | * `prependLines`, `splitBy` 9 | 10 | ... and some others you'll have to check in the [source `src/main/scala/sext/package.scala`](src/main/scala/sext/package.scala) 11 | 12 | #Using 13 | Add the following Maven dependency 14 | 15 | 16 | com.github.nikita-volkov 17 | sext 18 | 0.2.3 19 | 20 | 21 | or SBT dependency 22 | 23 | libraryDependencies += "com.github.nikita-volkov" % "sext" % "0.2.4" 24 | 25 | (or the appropriate Gradle one) 26 | 27 | Add the following import statement to your files: 28 | 29 | import sext._ 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012, Nikita Volkov 2 | 3 | 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: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | 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. -------------------------------------------------------------------------------- /src/main/scala/sext/OrderedMap.scala: -------------------------------------------------------------------------------- 1 | package sext 2 | 3 | import collection.immutable.Queue 4 | 5 | object OrderedMap { 6 | def apply[A, B](elems: (A, B)*) = 7 | new OrderedMap(Map(elems: _*), Queue(elems.map(_._1): _*)) 8 | } 9 | /** 10 | * An ordered map implementation that should perform effectively on all operations except 11 | * removal, where it performs linearly. 12 | */ 13 | class OrderedMap[A, B]( 14 | map: Map[A, B], 15 | protected val order: Queue[A] 16 | ) extends Map[A, B] { 17 | def get(key: A) = 18 | map.get(key) 19 | def iterator = 20 | order.iterator.map(x => x -> map(x)) 21 | def +[B1 >: B](kv: (A, B1)) = 22 | new OrderedMap( 23 | map + kv, 24 | if( map.contains(kv._1) ) order else order enqueue kv._1 25 | ) 26 | def -(key: A) = 27 | new OrderedMap( 28 | map - key, 29 | order diff Queue(key) 30 | ) 31 | override def hashCode() = 32 | order.hashCode 33 | override def equals(that: Any) = 34 | that match { 35 | case that: OrderedMap[A, B] => 36 | order.equals(that.order) 37 | case _ => 38 | super.equals(that) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/scala/sext/Cli.scala: -------------------------------------------------------------------------------- 1 | package sext 2 | 3 | /** 4 | * Utilities for command-line applications 5 | */ 6 | object Cli { 7 | /** 8 | * Process the input args string-array while providing default values for unspecified args. 9 | * 10 | * Sample usage: 11 | * 12 | * @example {{{ 13 | * val (intArg, longArg, stringArg) = Cli.parseArgs(args, Seq("1", "100", "someString")){ 14 | * case Array(a, b, c) => (a.toInt, b.toLong, c) 15 | * } 16 | * }}} 17 | * @param args The value of `args` field provided by [[scala.App]] 18 | * @param defaults Default values for unspecified args 19 | * @param process A function which will process a complete list of args with defaults provided for skipped args 20 | * @tparam Z Result of `process` function 21 | * @return Result of `process` function 22 | */ 23 | def parseArgs 24 | [ Z ] 25 | ( args : Array[String], 26 | defaults : Seq[String] ) 27 | ( process : Array[String] => Z ) 28 | : Z 29 | = if( args.length <= defaults.length ) 30 | process(args ++ defaults.drop(args.length)) 31 | else 32 | throw new Exception("Too many args") 33 | 34 | 35 | } -------------------------------------------------------------------------------- /src/test/scala/sext/OrderedMapTest.scala: -------------------------------------------------------------------------------- 1 | package sext 2 | 3 | import org.scalatest._ 4 | import org.junit.runner.RunWith 5 | import org.scalatest.junit.JUnitRunner 6 | import collection.immutable.Queue 7 | 8 | @RunWith(classOf[JUnitRunner]) 9 | class OrderedMapTest extends FunSuite with Matchers { 10 | test("preserves order of creation") { 11 | val elems = (1 to 100) zip (1 to 100) 12 | OrderedMap(elems: _*).toSeq should equal (elems) 13 | } 14 | test("preserves order of addition") { 15 | val elems1 = (1 to 100) zip (1 to 100) 16 | val elems2 = (101 to 200) zip (101 to 200) 17 | (OrderedMap(elems1: _*) ++ OrderedMap(elems2: _*) toSeq) should equal(elems1 ++ elems2) 18 | } 19 | test("equals equal and have same hashCode") { 20 | val elems = (1 to 100) zip (1 to 100) 21 | 22 | OrderedMap(elems: _*) should equal (OrderedMap(elems: _*)) 23 | OrderedMap(elems: _*).hashCode should equal (OrderedMap(elems: _*).hashCode) 24 | } 25 | test("differently ordered don't equal and have different hashcodes") { 26 | val elems1 = (1 to 100) zip (1 to 100) 27 | val elems2 = elems1.reverse 28 | 29 | OrderedMap(elems1: _*) should not equal (OrderedMap(elems2: _*)) 30 | OrderedMap(elems1: _*).hashCode should not equal (OrderedMap(elems2: _*).hashCode) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/test/scala/sext/Demo.scala: -------------------------------------------------------------------------------- 1 | package sext 2 | 3 | object Demo extends App { 4 | 5 | case class A(a: Int, b: String, c: Seq[A]) 6 | val a = A(1, "adsf", A(5, "oiweur\nsadf", Nil) :: A(4, "", A(6, "dsf", Nil) :: Nil) :: A(9, "sdlfkjw", Nil) :: Nil) 7 | a.valueTreeString.trace() 8 | 9 | Map( 10 | "repo/" -> Map( 11 | "org/" -> Map( 12 | "eclipse/" -> Map( 13 | "e4/" -> Map( 14 | "xwt/" -> Seq( 15 | "0.9.1-SNAPSHOT/" -> Seq( 16 | "maven-metadata-local.xml", 17 | "maven-metadata-local.xml.md5", 18 | "maven-metadata-local.xml.sha1", 19 | "xwt-0.9.1-SNAPSHOT-sources.jar", 20 | "xwt-0.9.1-SNAPSHOT-sources.jar.md5", 21 | "xwt-0.9.1-SNAPSHOT-sources.jar.sha1", 22 | "xwt-0.9.1-SNAPSHOT.jar", 23 | "xwt-0.9.1-SNAPSHOT.jar.md5", 24 | "xwt-0.9.1-SNAPSHOT.jar.sha1", 25 | "xwt-0.9.1-SNAPSHOT.pom", 26 | "xwt-0.9.1-SNAPSHOT.pom.md5", 27 | "xwt-0.9.1-SNAPSHOT.pom.sha1" 28 | ), 29 | "maven-metadata-local.xml", 30 | "maven-metadata-local.xml.md5", 31 | "maven-metadata-local.xml.sha1" 32 | ) 33 | ) 34 | ) 35 | ) 36 | ) 37 | ).valueTreeString.trace() 38 | 39 | 40 | ("lsdkfj\nsdljfdkslf\n ldkf", 0, 3).valueTreeString.trace() 41 | 42 | } 43 | -------------------------------------------------------------------------------- /src/main/scala/sext/Benchmarking.scala: -------------------------------------------------------------------------------- 1 | package sext 2 | 3 | object Benchmarking { 4 | 5 | /** 6 | * Execute a function and return its result paired with execution time 7 | * @param f The function to execute 8 | * @tparam Z Function execution result 9 | * @return A pair of function result and running time in nanoseconds 10 | */ 11 | def benchmark [Z] ( f : => Z ) : (Z, Long) 12 | = { 13 | val start = System.nanoTime() 14 | val result = f 15 | val period = System.nanoTime() - start 16 | (result, period) 17 | } 18 | /** 19 | * Execute and get result of a function while printing the time it took to execute it 20 | * @param label A label to prepend the outputted time with 21 | * @param f A function to execute 22 | * @tparam Z Result of the executed function 23 | * @return Result of the executed function 24 | */ 25 | def benchmarkAndPrint [Z] ( label : String ) ( f : => Z ) : Z 26 | = benchmark(f) match {case (r, p) => println(label + ": " + p); r} 27 | /** 28 | * Execute and get result of function `f` while executing a function `g` on nanoseconds it took to execute function `f` 29 | * @param g Function on nanoseconds 30 | * @param f Function to measure execution time of 31 | * @tparam Z Result of function `f` 32 | * @return Result of function `f` 33 | */ 34 | def benchmarkDoing [Z] ( g : Long => Unit ) ( f : => Z ) : Z 35 | = benchmark(f) match {case (r, p) => g(p); r} 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/test/scala/sext/Test.scala: -------------------------------------------------------------------------------- 1 | package sext 2 | 3 | import org.scalatest._ 4 | import org.junit.runner.RunWith 5 | import org.scalatest.junit.JUnitRunner 6 | 7 | @RunWith(classOf[JUnitRunner]) 8 | class Test extends FunSuite with Matchers { 9 | import Test._ 10 | test("Tuple4 zipped and unzip4"){ 11 | val as = List("a1", "a2", "a3") 12 | val bs = List("b1", "b2", "b3") 13 | val cs = List("c1", "c2", "c3") 14 | val ds = List("d1", "d2", "d3") 15 | 16 | 17 | (as, bs, cs, ds).zipped.filterNot(_._1 == "a2").unzip4 should equal ( (List("a1", "a3"), List("b1", "b3"), List("c1", "c3"), List("d1", "d3")) ) 18 | 19 | } 20 | test("zipBy preserves root type"){ 21 | Seq(1,2,3).zipBy(_ + 3) should beInstanceOf[Seq[_]] 22 | Seq(1,2,3).zipBy(_ + 3) should not (beInstanceOf[Set[_]]) 23 | } 24 | test("mapKeys") { 25 | Map("b" -> 1, "c" -> 4, "a" -> 9).mapKeys(_ + "1") should 26 | equal(Map("b1" -> 1, "c1" -> 4, "a1" -> 9)) 27 | } 28 | test("toInstanceOf returns None for unmatching type"){ 29 | 8.toInstanceOf[String] should equal(None) 30 | } 31 | test("toInstanceOf returns Some for a matching type"){ 32 | 8.toInstanceOf[Int] should equal(Some(8)) 33 | } 34 | test("toInstanceOf returns Some for an inheriting type"){ 35 | List(1).toInstanceOf[Seq[Int]] should be ('defined) 36 | } 37 | test("toInstanceOf on mixins"){ 38 | (new A with B).toInstanceOf[B] should be ('defined) 39 | } 40 | test("toInstanceOf on general types as input"){ 41 | (Seq(new A with B, new A{}, new B {})) 42 | .flatMap{_.toInstanceOf[B]} 43 | .should( have size (2) ) 44 | } 45 | } 46 | object Test { 47 | trait A 48 | trait B 49 | 50 | import org.scalatest.matchers._ 51 | import reflect._ 52 | def beInstanceOf [A : ClassTag] 53 | = Matcher { x : Any => 54 | MatchResult( 55 | classTag[A].runtimeClass.isAssignableFrom(x.getClass), 56 | x.getClass + " is not an instance of " + classTag[A].runtimeClass, 57 | x.getClass + " is an instance of " + classTag[A].runtimeClass 58 | ) 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | com.github.nikita-volkov 8 | sext 9 | 0.2.6 10 | jar 11 | 12 | SExt 13 | A small extensions library for Scala 14 | http://www.github.com/nikita-volkov/sext 15 | 16 | 17 | 18 | MIT/X11 19 | http://www.github.com/nikita-volkov/sext/LICENSE 20 | 21 | 22 | 23 | 24 | 25 | nikita-volkov 26 | Nikita Volkov 27 | nikita.y.volkov@mail.ru 28 | 29 | 30 | 31 | 32 | scm:git:git@github.com:nikita-volkov/sext.git 33 | scm:git:git@github.com:nikita-volkov/sext.git 34 | scm:git:git@github.com:nikita-volkov/sext.git 35 | 36 | 37 | 38 | org.sonatype.oss 39 | oss-parent 40 | 7 41 | 42 | 43 | 44 | 45 | org.scalatest 46 | scalatest_2.12 47 | 3.0.1 48 | test 49 | 50 | 51 | junit 52 | junit 53 | 4.7 54 | test 55 | 56 | 57 | org.scala-lang 58 | scala-reflect 59 | [2.10,2.12.999) 60 | 61 | 62 | org.scala-lang 63 | scala-library 64 | [2.10,2.12.999) 65 | 66 | 67 | 68 | 69 | 70 | 71 | net.alchim31.maven 72 | scala-maven-plugin 73 | 3.2.2 74 | 75 | incremental 76 | true 77 | 78 | 79 | 80 | compile 81 | 82 | compile 83 | testCompile 84 | 85 | 86 | 87 | attach-javadocs 88 | 89 | doc 90 | doc-jar 91 | 92 | 93 | 94 | 95 | doc 96 | 97 | site 98 | 99 | 100 | 101 | 102 | org.apache.maven.plugins 103 | maven-surefire-plugin 104 | 2.12 105 | 106 | 107 | **/*Suite.class 108 | **/*Test.class 109 | **/*Tests.class 110 | **/*Spec.class 111 | **/*Specs.class 112 | 113 | 114 | 115 | 116 | org.apache.maven.plugins 117 | maven-source-plugin 118 | 2.1.2 119 | 120 | 121 | attach-sources 122 | package 123 | 124 | jar-no-fork 125 | 126 | 127 | 128 | 129 | 130 | org.apache.maven.plugins 131 | maven-gpg-plugin 132 | 1.4 133 | 134 | 135 | sign-artifacts 136 | 137 | sign 138 | 139 | 140 | 141 | 142 | true 143 | 144 | 145 | 146 | 147 | org.sonatype.plugins 148 | nexus-staging-maven-plugin 149 | 1.6.7 150 | true 151 | 152 | ossrh 153 | https://oss.sonatype.org/ 154 | true 155 | 156 | 157 | 158 | maven-release-plugin 159 | 2.2.2 160 | 161 | clean deploy site-deploy 162 | 163 | 164 | 165 | 166 | 167 | 168 | -------------------------------------------------------------------------------- /src/main/scala/sext/package.scala: -------------------------------------------------------------------------------- 1 | package sext 2 | 3 | import util.Try 4 | import reflect.runtime.universe._ 5 | import reflect.runtime.currentMirror 6 | import collection.GenTraversableOnce 7 | 8 | object `package` { 9 | 10 | implicit class SextMap [ A, B ] ( val a : Map[ A, B ] ) extends AnyVal { 11 | def filterValues(predicate: B => Boolean) 12 | = a.filter(pair => predicate(pair._2)) 13 | def withValuesFilter(predicate: B => Boolean) 14 | = a.withFilter(pair => predicate(pair._2)) 15 | def mapKeys[Z](f: A => Z) 16 | = a.map(pair => f(pair._1) -> pair._2) 17 | } 18 | 19 | implicit class SextTraversable 20 | [ A, B[A] <: Traversable[A] ] 21 | ( val a : B[A] ) 22 | extends AnyVal 23 | { 24 | def zipBy [ Z ] ( f : A => Z ) : B[(A, Z)] 25 | = a.map(x => x -> f(x)).asInstanceOf[B[(A, Z)]] 26 | } 27 | 28 | implicit class SextAny [ A ] ( val a : A ) extends AnyVal { 29 | def tap [ Z ] ( f : A => Z ) = { f(a); a } 30 | 31 | /** 32 | * Is the value empty. Equivalent to monoid zero. 33 | */ 34 | def isEmpty : Boolean 35 | = a match { 36 | case null => true 37 | case x: Boolean => !x 38 | case x: Byte => x == 0.toByte 39 | case x: Short => x == 0.toShort 40 | case x: Char => x == 0.toChar 41 | case x: Int => x == 0 42 | case x: Long => x == 0l 43 | case x: Float => x == 0f 44 | case x: Double => x == 0d 45 | case x: Product => x.productIterator.forall(_.isEmpty) 46 | case x: GenTraversableOnce[_] => x.isEmpty 47 | case _ => false 48 | } 49 | 50 | def notNull = Option(a) 51 | 52 | def notEmpty = if (a.isEmpty) None else Some(a) 53 | 54 | def satisfying(p: A => Boolean) : Option[A] 55 | = if (p(a)) Some(a) else None 56 | 57 | def trace [ Z ] ( f : A => Z = (x : A) => x.treeString ) 58 | = { Console.println(f(a)); a } 59 | 60 | def trying [ Z ] ( f : A => Z ) = Try(f(a)).toOption 61 | 62 | def unfold 63 | [ Z ] 64 | ( f : A => Option[(Z, A)] ) 65 | : Stream[Z] 66 | = f(a) map {case (b, a) => b #:: (a unfold f)} getOrElse Stream() 67 | 68 | def unfold1 69 | ( f : A => Option[A] ) 70 | : Stream[A] 71 | = f(a) map (a => a #:: (a unfold1 f)) getOrElse Stream() 72 | 73 | def iterate 74 | ( f : A => A ) 75 | : Stream[A] 76 | = a #:: (f(a) iterate f) 77 | 78 | def foldTo 79 | [ Z ] 80 | ( b : Traversable[Z] ) 81 | ( f : (Z, A) => A) 82 | = (b foldRight a)(f) 83 | 84 | def foldFrom 85 | [ Z ] 86 | ( b : Traversable[Z] ) 87 | ( f : (A, Z) => A) 88 | = (b foldLeft a)(f) 89 | 90 | } 91 | 92 | implicit class SextAnyToInstanceOf[ A : TypeTag ]( x : A ) { 93 | /** 94 | * Get an option of this value as of instance of type `T` 95 | * @tparam T A type to see this value as 96 | * @return `Some(a)` if `a` is an instance of `T` and `None` otherwise 97 | */ 98 | def toInstanceOf[ T : TypeTag ] : Option[T] 99 | = { 100 | def test 101 | = currentMirror.runtimeClass(typeOf[T]) match { 102 | case c if c.isPrimitive => typeOf[A] <:< typeOf[T] 103 | case c => c.isAssignableFrom(x.getClass) || typeOf[A] <:< typeOf[T] 104 | } 105 | if( test ) Some( x.asInstanceOf[T] ) 106 | else None 107 | } 108 | } 109 | implicit class SextAnyTreeString [ A ] ( a : A ) { 110 | 111 | private def indent ( s : String ) 112 | = s.lines.toStream match { 113 | case h +: t => 114 | ( ("- " + h) +: t.map{"| " + _} ) mkString "\n" 115 | case _ => "- " 116 | } 117 | 118 | /** 119 | * @return A readable string representation of this value 120 | */ 121 | def treeString 122 | : String 123 | = a match { 124 | case x : Traversable[_] => 125 | x.stringPrefix + ":\n" + 126 | x.view 127 | .map{ _.treeString } 128 | .map{ indent } 129 | .mkString("\n") 130 | case x : Product if x.productArity == 0 => 131 | x.productPrefix 132 | case x : Product => 133 | x.productPrefix + ":\n" + 134 | x.productIterator 135 | .map{ _.treeString } 136 | .map{ indent } 137 | .mkString("\n") 138 | case null => 139 | "null" 140 | case _ => 141 | a.toString 142 | } 143 | 144 | /** 145 | * @return A readable string representation of this value of a different format to `treeString` 146 | */ 147 | def valueTreeString 148 | : String 149 | = a match { 150 | case (k, v) => 151 | k.valueTreeString + ":\n" + 152 | v.valueTreeString 153 | case a : TraversableOnce[_] => 154 | a.toStream 155 | .map(_.valueTreeString) 156 | .map(indent) 157 | .mkString("\n") 158 | case a : Product => 159 | val b 160 | = currentMirror.reflect(a).symbol.typeSignature.members.toStream 161 | .collect{ case a : TermSymbol => a } 162 | .filterNot(_.isMethod) 163 | .filterNot(_.isModule) 164 | .filterNot(_.isClass) 165 | .map( currentMirror.reflect(a).reflectField ) 166 | .map( f => f.symbol.name.toString.trim -> f.get ) 167 | .reverse 168 | collection.immutable.ListMap(b: _*).valueTreeString 169 | case null => 170 | "null" 171 | case _ => 172 | a.toString 173 | } 174 | 175 | } 176 | 177 | implicit class SextString 178 | ( val s : String ) 179 | extends AnyVal 180 | { 181 | def notEmpty 182 | = if( s.isEmpty ) None else Some(s) 183 | def indent 184 | ( i : Int ) 185 | = prependLines(" " * i) 186 | def prependLines 187 | ( p : String ) 188 | = s.lines 189 | .reduceOption{ _ + "\n" + p + _ } 190 | .map{ p + _ } 191 | .getOrElse( p ) 192 | def splitBy 193 | ( splitter : String ) 194 | : (String, String) 195 | = s.indexOf(splitter) match { 196 | case -1 => (s, "") 197 | case i => s.splitAt(i) match { case (a, b) => (a, b.drop(splitter.size)) } 198 | } 199 | } 200 | 201 | implicit class SextBoolean ( val a : Boolean ) extends AnyVal { 202 | def option [ Z ] ( b : Z ) : Option[Z] = if( a ) Some(b) else None 203 | } 204 | 205 | implicit class SextTuple4 206 | [ A, B, C, D ] 207 | ( val t : (Iterable[A], Iterable[B], Iterable[C], Iterable[D]) ) 208 | extends AnyVal 209 | { 210 | def zipped 211 | = t._1.toStream 212 | .zip(t._2).zip(t._3).zip(t._4) 213 | .map{ case (((a, b), c), d) => (a, b, c, d) } 214 | } 215 | 216 | implicit class SextIterable 217 | [ A, B, C, D ] 218 | ( val ts : Iterable[(A, B, C, D)] ) 219 | extends AnyVal 220 | { 221 | def unzip4 222 | = ts.foldRight((List[A](), List[B](), List[C](), List[D]()))( 223 | (a, z) => (a._1 +: z._1, a._2 +: z._2, a._3 +: z._3, a._4 +: z._4) 224 | ) 225 | } 226 | 227 | implicit class SextInt ( val a : Int ) { 228 | def times ( f : => Unit ) = (0 to a).foreach(_ => f) 229 | } 230 | 231 | /** 232 | * Useful for wrapping a function and passing as lambda when partially applied 233 | */ 234 | def trying [ A, Z ] ( f : A => Z ) ( a : A ) = a trying f 235 | 236 | def memo [ A, Z ] ( f : A => Z ) = { 237 | // a WeakHashMap will release cache members if memory tightens 238 | val cache = new collection.mutable.WeakHashMap[A, Z] 239 | x : A => synchronized { cache.getOrElseUpdate( x, f(x) ) } 240 | } 241 | 242 | } 243 | --------------------------------------------------------------------------------