Scala has this nice feature, where you can use interpolated string to match on some string and get some values from it:
case class Point(x: Int, y: Int)
val input = "3,5"
val patmat =
input match
case s"${x},${y}" => println(Point(x, y))
case _ => println("Nope")
However, this code would not compile, because Point need to get integers, not string, and type of x and y binding is a String.
Of course, one can just use x.toIntOption, but this would mean that the code above is less readable (and way more ugly).
So I propose either this:
make it so that s"${x : Int}, ${y: Int}" is treated differently in StringContext.unapply, then it would mean this x should be casted somehow to Int, if possible
or that:
add unapply method for every type that has method .to<type>Option in String, this would the following would be valid then: s"${Int(x)}, ${Int(y)}"
The first proposal is kind of ugly and I don’t actually know how to implement that. The latter is much better IMO, as it would not break anything and it is kind of obvious approach (and even useful outside of this case).
Am I missing something here, perhaps a proper solution to this problem?
What do you think about it?
The problem with that is that it would apply in every pattern match, not just in string interpolators. And that means that
def foo(s: Any, y: Int): Int = s match {
case Int(x) => x + y
case x: String => x.length() + y
}
foo("123", 5)
would return 128 and not 8.
That would be very surprising to me. If an Int(x) extractor existed, I would expect it to only match Ints; not Strings that happen to be parsed as Ints.
I wouldn’t find this behavior surprising. I guess I’d read Int(x) as "x can be interpreted as an Int".
For me, x: Int would be the pattern that only matches Ints.
implicit val intParser = ParseString[Int](_.toIntOption)
val patmat =
input match
case s"${x:Int}, ${y:Int}" => println(Point(x, y))
case _ => println("Nope")
Yep, I am aware of that. But creating those for every codebase for every type that I would like to match is kinda wrong. Creating it only when I need is overkill since I could just use .toIntOption.
you don’t read case Foo(x) as “x can be interpreted as a Foo”. You read it as "it is a Foo, and it contains an x which is actually an Int.
With this proposal, case Int(x) and case Foo(x) would be inconsistent. If anything, the proposed extractor should be called IntString (something that is a String and that contains an Int).
For whatever it’s worth, we use Scala UUID at work, and it includes the appropriate extractor to do this:
str match {
case UUID(uuid) => ...
case notUUID => ...
}
I don’t recall this ever causing confusion. Nobody expects UUID to be a case class, so not behaving like one doesn’t raise any eyebrows. My intuition is that Int#unapply would be received in a very similar manner.
I personally really like s"${x : Int},${y: Int}", I think it’s clear what it does:
It’s an interpolated string, so you know x and y have to be converted from String anyways, and you don’t add matchers that could be used outside of a context where strings are involved, which could be confusing as @sjrd pointed out.
It could require something akin to Conversion[String, A], maybe a given Parser[A] ?
This would allow easy extensions to user-defined types
Without term inference, there’s also util.CommandLineParser.FromString, but to extend it, you need to subclass it, which is not ideal