Is there an implicit Conversion[A,A] somewhere?

I’m writing a parser that needs a way to convert strings into tokens:

def parse1[A](line: String)(using conv: String => A): A = conv(line)

(The actual parser does a little more…)

I can use it as parse1("0")(using _.toInt) but this also works:

val a = parse1("a")

because identity is implicitly available as String => String.

So far, so good, but one little issue with this approach is implicit functions popping out of nowhere. For instance, inside Scalatest, a conversion from String to Equalizer got in the way. As a result, using parse(...) instead of parse[SomeType](...) wasn’t caught at compile-time and let to weird test failures.

In an attempt to improve robustness, I rewrote the parser as:

def parse2[A](line: String)(using Conversion[String, A]): A = line.convert

parse2("0")(using _.toInt) still works but I was surprised that this didn’t:

val b = parse2[String]("b")

Isn’t there a given somewhere that can play the part of Conversion[String,String] (or Conversion[A,A] for that matter)?

(Side question: are String => A and Conversion[String, A] equally wrong as a design choice in this case? Should a specific type be introduced instead?)

EDIT: I’ve answered the second question myself: it’s way too dangerous to have a Conversion[String, Int] floating around… :open_mouth: I’m still curious about the failure to summon a Conversion[A, A] when needed, though.

I, too, noticed this recently in some context.

The implicit identity is due to how implicit evidence of “conformity” is provided in Predef.

Conversion is for when typechecking fails, so Conversion[A, A] is not useful.

I don’t immediately see how to write an overload that is not ambiguous for a string arg, however.

That makes sense. I suppose Conversion is not to be used for implicit arguments.

I ended up with a user-defined type to avoid ambiguities and suitable givens in the companion object, and everything’s working fine.

It is in general recommended to have given/using types be ones specific to the library, to avoid conflicts between libraries (what you experienced with Scalatest).

I’d therefore recommend something like the following:

trait Parsable[A]:
  def parse(s: String): A

given Parsable[String] = identity // function SAM-converted to instance of Parsable

def parse3[A](line: String)(using conv: Parsable[A]): A = conv(line)

Or of course a two element version of Parsable, but it doesn’t seem like that is needed in your case

You can then add the following if you wish to promote implicit conversions to Parsables (but I’m not sure I would recommend):

given [A](using conv: Conversion[String, A]): Parsable[A] = (s: String) => conv(s)
1 Like

Yes, I ended up with something like that:

trait Conv[A]:
   def apply(token: String): A

object Conv:
   given Conv[String] = identity
   given Conv[Int]    = _.toInt

Interesting! I didn’t know a given could have a context like that. (Though that’s the old “chaining of implicits”, I suppose.) That’s something I’ll need to remember. Thanks.

Glad I could be helpful !

Note that this new form also lends itself to the Type Class notation:

trait Parsable[A]:
  def parse(s: String): Option[A]

def parse[A](using ev: Parsable[A])(s: String) = ev.parse(s)

def or[A : Parsable, B : Parsable](s: String): Option[Either[A, B]] =
  parse[A](s) match // notice here
    case Some(a) => Some(Left(a))
    case None =>
      parse[B](s) match // and here
        case Some(b) => Some(Right(b))
        case None => None