Something like "Power assert" in Scala similar to Groovy? - scala

I'm wondering if there is something that can give me results similar to Groovy's nice power assert statement.
> assert ["1", '2']*.size() == [2, 3]
Result: Assertion failed:
assert ["1", '2']*.size() == [2, 3]
| |
[1, 1] false
AFAIK there is no support for such thing neither in language, nor in
scalatest, which I'm currently using.
But maybe someone can suggest some side library doing that? It's a pet project, so experimental and not well-supported libs are fine.
EDIT: I know about matchers (scalatest ones, or even plain-java hamcrest matchers). I find them verbose to write and that their output lacks details.
The example above shows intermediate computation steps, facilitating detection of errors. It shows you what's wrong with tested code with more details.
I expect, that introducing such behaviour will require having information about expression AST at runtime. But I suppose, that this information can be "baked" compile time with usage of macroses.
I.e. if we have expression assert a + b == c scala (or some macro extension I'm looking for) can rewrite it to something like:
if (!(a + b == c)) {
// detailed message is
// compute a
// compute b
// compute a + b
// compute c
// compute a + b == c
// Make it pretty.
throw new AssertionFailedException(prettyDetailedMessage)
}
So I'm looking if it's already implemented, and if yes - where.

In ScalaTest, you can use DiagrammedAssertions.
See http://www.scalatest.org/release_notes/2.2.0#diagrammedAssertions
This is based on Expecty which is a macro-based implementation of Spock's power assertions. See https://github.com/pniederw/expecty

Specs2 matchers do a good job with error messages:
class Specs2Specification extends Specification {
"specs2 assertion" should {
"fail" in {
List("1", "2").map(_.length) must_=== List(2, 3)
}
}
}
run output:
[info] Specs2Specification
[info]
[info] specs2 assertion should
[error] x fail
[error] List(1, 1) is not equal to List(2, 3)
[info]
[error] Added (2)
[error] 1
[error] 1
[info]
[error] Missing (2)
[error] 2
[error] 3
or
List("1", "2").map(_.length) must contain(exactly(2, 3)).inOrder
which produces
[error] x fail
[error] the values 2, 3 are not in order
There are lots of them and you can create custom ones.

Your groovy code snippet is literally translated to the following scala code (given that you're already using scalatest):
assert((List("1", "2") map (_.length)) === List(2, 3))
It produces the following error message:
*** FAILED ***
List(1, 1) did not equal List(1, 3)

Related

scala "Illegal start of simple expression" in for comprehension with if

I am in the process of implementing a simple in-memory, Redis-like KeyValue store and experiencing a compilation failure on the if statement within the for comprehension following piece of code:
/*
Returns the specified elements of the list stored at key. The offsets start and
stop are zero-based indexes, with 0 being the first element of the list to n. */
def lrange(k: keyT, maxIdx:Int, minIdx:Int): List[valT] = {
val l = lookup(k)
//assert(maxIdx >= minIdx && maxIdx <= (l length) && minIdx >= 0, "invalid min or max argument. list size ")
for {
(x: valT, i: Int) <- l zipWithIndex //tried without explicit typing
if i <= maxIdx && i >= minIdx //tried indenting if
} yield x
}
The editor (IntelliJ) shows no errors, but I receive the following build error when attempting to build and run tests.
[INFO] --- scala-maven-plugin:3.3.2:compile (default) # DS4300Project3 ---
[INFO] .../Spring2019/DS4300/scala/DS4300Project3/src/main/scala:-1: info: compiling
[INFO] Compiling 3 source files to .../Spring2019/DS4300/scala/DS4300Project3/target/classes at 1550678144065
[ERROR] .../Spring2019/DS4300/scala/DS4300Project3/src/main/scala/com/rejevichb/homework3/KeyValStore.scala:70: error: illegal start of simple expression
[ERROR] if (i <= maxIdx) && (i >= minIdx) //tried indenting if
[ERROR] ^
[ERROR] one error found
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
Specifically:
KeyValStore.scala:70: error: illegal start of simple expression
Any guidance or insight into what is going wrong here is appreciated, as the solution is not clear to me.
This is exactly the reason why you should use postfix operators with caution.
for {
i <- "a" zipWithIndex
if true
} yield i
is parsed as
for { i <- ("a" zipWithIndex if true) } yield i
because the compiler attempts to interpret zipWithIndex as a binary infix operator, but then runs into if true, which is indeed not a simple expression.
Workarounds:
just don't use postfix ops, use a period:
for {
i <- "a".zipWithIndex
if true
} yield i
add a semicolon to force zipWithIndex to be interpreted as postfix op:
for {
i <- "a" zipWithIndex;
if true
} yield i
and then enjoy your feature warning:
warning: postfix operator zipWithIndex should be enabled
by making the implicit value scala.language.postfixOps visible.

Adding an item to scala.collection.mutable.HashMap

I tried to add an element to a Scala HashMap
val c2 = new collection.mutable.HashMap[String,Int]()
c2 += ("hh",1)
but the above gives me a compile error.
[error] found : String("hh")
[error] required: (String, Int)
[error] c2 += ("hh", 1)
[error] ^
[error] /scalathing/Main.scala:5: type mismatch;
[error] found : Int(1)
[error] required: (String, Int)
[error] c2 += ("hh", 1)
[error] ^
[error] two errors found
[error] (compile:compileIncremental) Compilation failed
[error] Total time: 3 s, completed Sep 1, 2016 1:22:52 AM
The pair I'm adding seems to be of the correct type as demanded by the HashMap. Why do I get a compile error?
The += operator is overloaded to work with variadic arguments. Therefore when the compiler sees c2 += ("hh", 1) it interprets that as two arguments being passed in, one of which is "hh" and the other of which is 1. You can fix that either by using the -> operator, i.e. c2 += ("hh" -> 1) or enclosing the tuple in another series of parantheses, i.e. c2 += (("hh, 1)).
Slightly gory details below as requested in the comments.
As for how all this works, in the case of mutable collections such as HashMap, += is simply an ordinary method called with operator syntax (i.e. spaces instead of a .) or "infix notation" as the Scala community calls it, as any method in Scala can be. It is provided by the Growable trait which mutable collections mix in. You can see on the documentation for Growable both the single argument += method and the variadic method. In other words the following code would have also worked.
c2.+=(("hh", 1))
Not all +=s are created equal however. += commonly shows up in vars as well. Although it can be called with method syntax ., it's magic syntax sugar implemented directly by the Scala compiler. In particular any nonalphanumeric name followed by an = gets desugared. x $NAME= y becomes x = x.$NAME(y). In this case $NAME= is variadic if and only if $NAME is variadic.
var i = 0
i += 1
i.+=(1) // Also compiles
case class SuperInt(raw: Int) {
def &*&(x: SuperInt) = SuperInt(raw + x.raw)
}
var x = SuperInt(1)
x &*&= SuperInt(1) // This works
x.&*&=(SuperInt(1)) // As does this
x &*&= (SuperInt(1), SuperInt(1)) // Does not compile because &*& is not variadic

Scalacheck verbosity

Taking this example right from the User Guide of Scalacheck:
scala> import org.scalacheck.Prop.{forAll, BooleanOperators}
scala> val propTrivial = forAll { n: Int =>
| (n == 0) ==> (n == 0)
| }
scala> propTrivial.check
! Gave up after only 4 passed tests. 500 tests were discarded.
I'd like to see the actual value that caused the test to fail. I have a similar test case in my project that produces a similar (useless) message.
I tried adding the verbosity option to sbt:
testOptions in Test += Tests.Argument(TestFrameworks.ScalaCheck, "-verbosity", "5")
and also tried calling the property like
testOptions in Test += Tests.Argument(TestFrameworks.ScalaCheck, "-verbosity", "5")
But neither works. I cannot get any more output.
In this case, no value caused the test to fail, but ScalaCheck gave up because it could not find enough values to try. This is why it says 500 tests were discarded - it's not a useless error message, it's just saying that the generated values did not match your n == 0 precondition.
If you try with a test that does fail, it will tell you the failing test:
scala> forAll { n: Int => n > 0 }
res0: org.scalacheck.Prop = Prop
scala> res0.check
! Falsified after 2 passed tests.
> ARG_0: -2147483648

Chisel Programming Error

I am having a problem in my Chisel code, I tried the following approach
deqReg := Cat((0 until ports).map(ownReg === Cat(io.configVal(portBits*(_) + 2),io.configVal(portBits*(_)+ 1), io.configVal(portBits*(_)))))
but I am getting the following error when running the above code
[error] /home/jayant/Dropbox/FIFO/fifo.scala:24: missing parameter type for expanded function ((x$1) => portBits.$times(x$1).$plus(2))
[error] deqReg := Cat((0 until ports).map(ownReg === Cat(io.configVal(portBits*(_) + 2),io.configVal(portBits*(_)+ 1), io.configVal(portBits*(_)))))
[error] ^
[error] one error found
[error] (compile:compileIncremental) Compilation failed
[error] Total time: 2 s, completed 4 Sep, 2015 12:31:40 PM
can any one tell what is this error and how to correct it.
You have multiple nested functions in your map which would make it impossible for the Scala compiler to infer the type of the argument. In other words you cannot user the "_" placeholder here. The placeholder just replaces the argument of the innermost function within the expression. Try a fully specified anonymous function (or a partial function) like this:
deqReg := Cat((0 until ports).map{ case i:Int => ownReg === Cat(io.configVal(portBits*i + 2), io.configVal(portBits*i + 1), io.configVal(portBits*i))})
Scala is a quite powerful language and you'd most probably be able to find a more elegant way to write that code.

Scala Compilation Error using Partition

scala> val set = Set("apricot", "banana", "clementine", "durian", "fig", "guava", "jackfruit", "kiwi", "lime", "mango")
set: scala.collection.immutable.Set[java.lang.String] = Set(banana, durian, fig, jackfruit, lime, mango, clementine, apricot, kiwi, guava)
scala> set.partition(_ length > 5)
<console>:1: error: ')' expected but integer literal found.
set.partition(_ length > 5)
^
scala> set.partition(_.length > 5)
res5: (scala.collection.immutable.Set[java.lang.String], scala.collection.immutable.Set[java.lang.String]) = (Set(banana, durian, jackfruit, clementine, apricot),Set(fig, lime, mango, kiwi, guava))
Can someone please explain why does it complain when I execute
set.partition(_ length > 5)
and not when I execute
set.partition(_.length > 5)
I have also tried the following with little success:
scala> set.partition((_ length) > 5)
<console>:9: error: missing parameter type for expanded function ((x$1) => x$1.length)
set.partition((_ length) > 5)
^
When you drop the dot, Scala assumes you have a one-parameter method. In other words, when you say _ length > 5 it thinks that length is a method requiring one argument, that > is that argument, and then it doesn't konw what to do with the 5.
Notice that this is similar to when you write 5 + 5. This statement is the same as writing 5.+(5), but you are dropping the dot and parentheses. Scala notices the missing dot and assumes (correctly) that + is a method requiring a single argument.
If you write "abc" length by itself, then there is nothing for Scala to assume is the argument, so it then realizes that length doesn't require one.
So:
"abc".length // fine
"abc" length // fine
"abc".length > 4 // fine
("abc" length) > 4 // fine
"abc" length > 4 // error!
Thus, you need either a dot or parentheses to tell the compiler that "abc" and length go together and have no additional argument.
In terms of coding style, I suggest you always use the dot for zero-arg methods. I find it more readable and it will help you to avoid compilation errors such as this one.
When you write:
set.partition(_ length > 5)
// ^ object
// ^ method
// ^ parameter
it treats length as a method that receives one parameter, >.
While when you write:
set.partition(_.length > 5)
// ^------^ object
// ^ method
// ^ parameter
it treats _.length as the object, > is the parameter, and 5 is the argument.