├── .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 |
--------------------------------------------------------------------------------