Inspired by this, I was wondering if we can have type-safe string interpolations in Scala (maybe using macros)?
For example, I want to have something like this
def a[A] = ???
val greetFormat = f"Hi! My name is ${a[String]}. I am ${a[Int]} years old"
greetFormat.format("Rick", 27) // compiles
//greetFormat.format("Rick", false) // does not compile
//greetFormat.format(27, "Rick") // does not compile
//greetFormat.format("Rick", 27, false) // does not compile
//greetFormat.format("Rick") // does not compile or is curried?
The f string interpolator is already implemented with a macro.
This can be demonstrated inside of the REPL:
scala> val b = "not a number"
b: String = not a number
scala> f"$b%02d"
<console>:9: error: type mismatch;
found : String
required: Int
f"$b%02d"
^
Just wrap it in a function.
def greet(name: String, age: Int) = s"Hi! My name is $name. I am $age years old"
You can supply implicits to the f-interpolator:
scala> case class A(i: Int)
defined class A
scala> implicit def atoi(a: A): Int = a.i
warning: there were 1 feature warning(s); re-run with -feature for details
atoi: (a: A)Int
scala> f"${A(42)}%02d"
res5: String = 42
See also Travis Brown's examples and solution for using regex group names in extractions. It took me about a minute to steal that great idea.
"a123bc" match {
case res # xr"(?<c>a)(?<n>\d+)(?<s>bc)" => assert {
res.c == 'a' && res.n == 123 && res.s == "bc"
}
}
For the record, on the composition side, I would like:
val a = A(Rick, 42)
val greeter = f"Hi! My name is $_. I am ${_}%d years old"
greeter(a, a)
But it was deemed too much for the poor underscore. You'll have to write the function as in the other answer.
Your form, in which your macro sees "${a[Int]}" and writes a function with an Int param, doesn't look hard to implement.
Other features of the f-interpolator include other static error checking:
scala> f"$b%.02d"
<console>:19: error: precision not allowed
f"$b%.02d"
^
and support for Formattable:
scala> val ff = new Formattable { def formatTo(fmtr: Formatter, flags: Int, width: Int, precision: Int) = fmtr.format("%s","hello, world") }
ff: java.util.Formattable = $anon$1#d2e6b0b
scala> f"$ff"
res6: String = hello, world
A quick macro might emit (i: Int) => f"${ new Formattable {...} }".
Related
I mistakenly concatted a string with an Option[String] while coding in scala.
I expected as a strongly typed language, scala would not allow me to do such operation.
This is what I tried.
This works
scala> val a:String = "aaa"
val a: String = aaa
scala> val b:Option[String] = Some("bbbb")
val b: Option[String] = Some(bbbb)
scala> a + b
val res0: String = aaaSome(bbbb)
scala> val c:Option[String] = None
val c: Option[String] = None
scala> val d = a + c
val d: String = aaaNone
scala> val e = 1
val e: Int = 1
scala> a + e
val res2: String = aaa1
while this does not work
scala> val f:Option[String] = Some("ffff")
val f: Option[String] = Some(ffff)
scala> val g:Option[String] = None
val g: Option[String] = None
scala> f + g
^
error: type mismatch;
found : Option[String]
required: String
Why does scala allow such behavior? Dynamically typed languages like python will stop me from adding strings to int types, None types or any type other than strings. Curious if this design is intentional? If so why?
Scala contains an implicit class any2stringadd in it's Predef package. This class is responsible for these concatenation operations.
implicit final class any2stringadd[A](private val self: A) extends AnyVal {
def +(other: String): String = String.valueOf(self) + other
}
What it means is that, by default, scope contains a method + which can concatenate value of any type A with string by converting value of this type to string via String.valueOf(...).
I can't speak of design choices, I agree that this might be an unexpected behavior. The same applies to Scala's native == method. For example, this code compiles just ok: Some("a") == "b". This can lead to nasty bugs in filtering and other methods.
If you want to eliminate this behavior I suggest you take a look at https://typelevel.org/cats/ library, which introduces different typeclasses that can solve this problem.
For example, for string concatenation you can use Semigroup typeclass (which has tons of other useful use-cases as well):
import cats.Semigroup
Semigroup[String].combine("a", "b") // works, returns "ab"
Semigroup[String].combine("a", Some("b")) // won't work, compilation error
This looks tedious, but there is a syntactic sugar:
import cats.implicits._
"a" |+| "b" // works, returns "ab"
"a" |+| Some("b") // won't work, compilation error
// |+| here is the same as Semigroup[String].combine
The same thing applies to == method. Instead you can use Eq typeclass:
import cats.implicits._
"a" == Some("b") // works, no error, but could be unexpected
"a" === Some("b") // compilation error (Cats Eq)
"a" === "b" // works, as expected
"a" =!= "b" // same as != but type safe
Suppose I want to create a NonZero type so that my integer division function is total:
def div(numerator: Int, denominator: NonZero): Int =
numerator / denominator.value
I can implement this by creating a NonZero class with a private constructor:
class NonZero private[NonZero] (val value : Int) { /*...*/ }
And a helper object to hold a Int => Option[NonZero] constructor, and an unapply so it can be used in match expressions:
object NonZero {
def build(n:Int): Option[NonZero] = n match {
case 0 => None
case n => Some(new NonZero(n))
}
def unapply(nz: NonZero): Option[Int] = Some(nz.value)
// ...
}
build is fine for runtime values, but having to do NonZero.build(3).get for literals feels ugly.
Using a macro, we can define apply only for literals, so NonZero(3) works, but NonZero(0) is a compile-time error:
object NonZero {
// ...
def apply(n: Int): NonZero = macro apply_impl
def apply_impl(c: Context)(n: c.Expr[Int]): c.Expr[NonZero] = {
import c.universe._
n match {
case Expr(Literal(Constant(nValue: Int))) if nValue != 0 =>
c.Expr(q"NonZero.build(n).get")
case _ => throw new IllegalArgumentException("Expected non-zero integer literal")
}
}
}
However this macro is less useful than it could be, as it only allows literals, not compile-time constant expressions:
final val X: Int = 3
NonZero(X) // compile-time error
I could pattern match on Expr(Constant(_)) in my macro, but then what about NonZero(X + 1)? I'd rather not have to implement my own scala expression evaluator.
Is there a helper or some easy way to determine if the value of an expression given to a macro is known at compile time (what C++ would call constexpr)?
If you ignore macros, then in Scala, only types exist at compile time, and only values exist at runtime. You can do type-level tricks to encode numbers as types at compile time, e.g. Type Level Programming in Scala
Here's a simplified version of the above Peano arithmetic example. First, we define a typeclass that shows how some type can convert to an integer.
#annotation.implicitNotFound("Create an implicit of type TValue[${T}] to convert ${T} values to integers.")
final class TValue[T](val get: Int) extends AnyVal
Then, we define the Peano 'zero' type and show how it can convert to a runtime integer 0:
case object TZero {
implicit val tValue: TValue[TZero.type] = new TValue(0)
}
Then the Peano 'successor' type and how it can convert to a runtime integer 1 + previous value:
case class TSucc[T: TValue]()
object TSucc {
implicit def tValue[TPrev](implicit prevTValue: TValue[TPrev]): TValue[TSucc[TPrev]] =
new TValue(1 + prevTValue.get)
}
Then test safe division:
object Test {
def safeDiv[T](numerator: Int, denominator: TSucc[T])(implicit tValue: TValue[TSucc[T]]): Int =
numerator / tValue.get
}
Trying it out:
scala> Test.safeDiv(10, TZero)
<console>:14: error: type mismatch;
found : TZero.type
required: TSucc[?]
Test.safeDiv(10, TZero)
^
scala> Test.safeDiv(10, TSucc[String]())
<console>:14: error: Create an implicit of type TValue[String] to convert String values to integers.
Test.safeDiv(10, TSucc[String]())
^
scala> Test.safeDiv(10, TSucc[TZero.type]) // 10/1
res2: Int = 10
scala> Test.safeDiv(10, TSucc[TSucc[TZero.type]]) // 10/2
res3: Int = 5
As you can imagine though, this can get verbose fast.
som-snytt's advice to check out ToolBox.eval led me to Context.eval, which the helper I'd been wanting:
object NonZero {
// ...
def apply(n: Int): NonZero = macro apply_impl
def apply_impl(c: Context)(n: c.Expr[Int]): c.Expr[NonZero] = try {
if (c.eval(n) != 0) {
import c.universe._
c.Expr(q"NonZero.build(n).get")
} else {
throw new IllegalArgumentException("Non-zero value required")
}
} catch {
case _: scala.tools.reflect.ToolBoxError =>
throw new IllegalArgumentException("Unable to evaluate " + n.tree + " at compile time")
}
}
So now I can pass NonZero.apply constants and expressions made with constants:
scala> final val N = 3
scala> NonZero(N)
res0: NonZero = NonZero(3)
scala> NonZero(2*N + 1)
res1: NonZero = NonZero(7)
scala> NonZero(N - 3)
IllegalArgumentException: ...
scala> NonZero((n:Int) => 2*n + 1)(3))
IllegalArgumentException: ...
While it'd be nice if eval could handle pure functions like the last example above, this is good enough.
Embarrassingly, reviewing and retesting my earlier code from the question proved that my original macro handled the same expressions just as well!
My assertion that final val X = 3; NonZero(X) // compile-time error was just wrong, since all the evaluation was being handled by inlining (as som-snytt's comment implied).
When trying to define a generic method def f[T] (x:T) = x + 1 Scala gives below error
<console>:8: error: type mismatch;
found : Int(1)
required: String
def f[T] (x:T) = x + 1
^
The question is why Scala is assuming that it should be a String?
What if we want a function that can do +1 to Int, Char and String. I know I can do something like below, but it would work only on Int and Char.
def f[T <% Int](x:T) = x + 1
So what's the reason of this error and how to handle it in generic way.
The question is why Scala is assuming that it should be a String?
I can't guarantee this analysis, but it appears that Scala is applying the Predef.any2stringadd() implicit conversion to x, in an attempt to turn it into something that supports the + operator.
Here's a variant that will compile, and which demonstrates that implicit conversion:
scala> def f(x:Any) = { x + "1" }
f: (x: Any)String
scala> f("foo")
res3: String = foo1
scala> f(123)
res0: String = 1231
scala> f(classOf[String])
res2: String = class java.lang.String1
What if we want a function that can do +1 to Int, Char and String.
What does it mean to add 1 to any of these values?
If you simply want to invoke the + operator, then you need to use the match operator to select different behaviors depending on the actual type. This is because, while the name + is used for both, there's no common behavior between strings and numbers.
On the other hand, perhaps you want to deal with numbers that can be provided as either strings or numeric values (in which case, why Char?). To make that work, you need an implicit function that converts Any to a number.
scala> implicit def any2int(x:Any) : Int = { x.toString.toInt }
warning: there were 1 feature warning(s); re-run with -feature for details
any2int: (x: Any)Int
scala> def f(x:Any) : Int = { x + 1 }
f: (x: Any)Int
scala> f(123)
res0: Int = 124
scala> f("123")
res1: Int = 124
The return type of the function will always be Int as I assume so it should be that the compiler choose Int but why String.
If I define a simple stringToInt function and store it as a val, everything works as expected, e.g.
scala> def stringToInt1: (String => Int) = _.toInt
stringToInt1: String => Int
scala> stringToInt1("1")
res0: Int = 1
However, if I then make that implicit, it causes a stack overflow:
scala> implicit def stringToInt2: (String => Int) = _.toInt
stringToInt2: String => Int
scala> stringToInt2("1")
java.lang.StackOverflowError
at .stringToInt2(<console>:7)
at $anonfun$stringToInt2$1.apply(<console>:7)
at $anonfun$stringToInt2$1.apply(<console>:7)
...
At first I suspected that this was because the underscore wasn't resolving to what I expected, but that's not the case, as this style of implicit val works fine for the following simple function:
scala> implicit def plusTwo: (Int => Int) = _ + 2
plusTwo: Int => Int
scala> plusTwo(2)
res2: Int = 4
If I define the parameter explicitly, no stack overflow:
scala> implicit def stringToInt3(s: String) = s.toInt
stringToInt3: (s: String)Int
scala> stringToInt3("1")
res3: Int = 1
(If trying this yourself and this last case stack overflows, restart the scala console and redo this last step)
So my question is, why is the original implicit not correctly resolving?
Edit
Ok digging a little deeper here, it seems that the problem is with the implicit conversion from String to StringOps. If we cut that out, it works fine:
scala> import scala.collection.immutable.StringOps
import scala.collection.immutable.StringOps
scala> implicit def stringToInt4: (String => Int) = new StringOps(_).toInt
stringToInt4: String => Int
scala> stringToInt4("1")
res4: Int = 1
But why would that implicit conversion be causing the issue?
Adding to the other replies.
There is no toInt method on String. Scala has to find an implicit conversion that will yield a type that has a toInt method.
Usually the StringOps conversion provides this toInt.
However Int has a toInt too, so scala finds your conversion from String => Int and decides that it has precedence over the StringOps conversion, thus applying it recursively.
This is why StringToInt4 works, as you explicitly tell the compiler what conversion you want. Maybe you could write it as: implicit def stringToInt5: (StringOps => Int) = _.toInt or check how implicits are resolved and how one takes precedence over the other.
Note what you are doing here:
scala> def stringToInt1: (String => Int) = _.toInt
stringToInt1: String => Int
You are defining a method (using def) that takes no parameters and returns a function from String to Int. Are you doing it this way on purpose, or is it just because you don't exactly understand the syntax?
You could have created a val instead:
val stringToInt1: (String => Int) = _.toInt
Now, stringToInt1 is a val that contains a function to convert String to Int.
With your stringToInt2, Scala is recursively applying the method. I don't know why it does that in the case of stringToInt2 but not with plusTwo.
Note that stringToInt3 is not the same thing as stringToInt1 and stringToInt2. It's a method that takes a String and returns an Int, not a method that takes no parameters and returns a function from String to Int.
I am trying to do:
MyObject.myMethod(_:MyType.myAttribute)
This fails with
type myAttribute is not a member of object MyObject
which is correct. The problem is that I want to call myMethod on myAttribute of _:MyType, not ascribe MyType:myAttribute to _. Can I somehow group the type ascription _:MyType? (_:MyType).myAttribute returns type MyType => classOf(myAttribute), which is not what I want.
Edit: I changed the title and text of this post to no longer refer to this as the associativity of the dot, which I believe was not correct.
Are you trying to create function (m: MyType) => MyObject.myMethod(m.myAttribute) using underscore?
If so, the problem is that MyObject.myMethod((_:MyType).myAttribute) means MyObject.myMethod((m:MyType) => m.myAttribute).
You can use infix notation:
MyObject myMethod (_:MyType).myAttribute
Proof it works:
scala> case class MyType(myAttribute: Int)
defined class MyType
scala> object MyObject {
| def myMethod(a: Int) = a.toString
| }
defined module MyObject
scala> MyObject myMethod (_:MyType).myAttribute
res0: MyType => java.lang.String = <function1>
scala> res0(MyType(1))
res1: java.lang.String = 1
scala> MyObject myMethod (MyType(1))
<console>:1: error: type mismatch;
found : MyType
required: Int
MyObject myMethod (_:MyType)
^
I'm not sure whether this illustrates or answers your question, but it's true.
My guess is that you expected your a.b(_.i) to be an anon func after you add an ascription (to type the parameter).
But the subexpr foils you by there is no other expression of syntactic category Expr which is properly contained in e and which itself properly contains u. (SLS 6.23)
Also, you can use scalac -Xprint:parser to see how it's taken.
object Foo {
def m(k: Int) = 7 * k
}
class Bar {
val i = 5
val What = i
}
object Bar {
type What = Int
}
object Test extends App {
Foo.m(_:Bar.What)
// this is not anon func placeholder syntax...
//Foo.m((_:Bar).What) // _ is in a subexpr
//Foo.m(_.i)
// ...for this
val f = (x: Bar) => Foo.m(x.i)
// InfixExpr is ok
val g = Foo m (_: Bar).i
val b = new Bar
println(f(b))
println(g(b))
}
Contrast, to illustrate what is being restricted:
scala> val f: (Int,Int)=>Int = _+_
f: (Int, Int) => Int = <function2>
scala> val g: Int=>Int = if (_ > 0) 1 else 2
<console>:7: error: missing parameter type for expanded function ((x$1) => x$1.$greater(0))
List(1,2,3).map((i: Int) => i * i)
EDIT
List(1,2,3).map((_: Int).unary_-)
EDIT 2
implicit class MyAttribute(i: Int) { def myMethod() = i * i }
List(1,2,3).map((_: Int).myMethod.unary_-)
Explanation
I used implicit class (Scala-2.10) to add myMethod on Int, after that unary "-" operation executed on result. You could add something like def wrap: MyAttribute to MyAttribute, and use it like (_: Int).wrap.method1.method2.method3.result.abs, for example.