scalaimplicit-conversionscala-implicits

Scala implicit conversion for object


Suppose that I have the following code snippet:

import scala.language.implicitConversions

sealed trait Command {
  val typeName: String
}

object Command {

  implicit def command2String(implicit c: Command): String =
    c.typeName

}

case object SendMessageCommand extends Command {
  override val typeName: String = "send_message"
}

I would like to compare String with Command descendants without explicit conversion. For example:

"sendMessage" == SendMessageCommand

Q1: Is it possible in Scala?

Q2: Can I define generic implicit conversion that will convert known type such as String to super class types (like Command in my case)?

For example:

implicit def string2Command(implicit s: String): Command = {
  case "send_message" => SendMessageCommand
  case _ => // some error thrown
}

P.S. I know that it is not idiomatic Scala, but it would be awesome to find out the right way.


Solution

  • Yes, its possible, but not with == because Scala support implicit conversion for these cases only: conversions to an expected type, conversions of the receiver of a selection, and implicit parameters and hence implicit is not applicable for comparison case. Instead, you have to create a comparison method in Command trait as below.

      sealed trait Command {
        val typeName: String
        def isEqual(c: Command) = c.typeName == this.typeName
      }
    
      object SendMessageCommand extends Command {
        override val typeName: String = "send_message"
      }
    
      implicit def stringToCommand(s: String) = new Command {override val typeName: String = s}
      implicit def commandToString(c: Command) = c.typeName
    
      val strToCmdCompare = "send_message" isEqual SendMessageCommand
      val cmdToStrCompare = SendMessageCommand isEqual "send_message"
    
      println(strToCmdCompare)    //print true
      println(cmdToStrCompare)    //print true
    

    Now, coming to your second question, yes its possible to define a generic implicit conversion to convert any given type into another type instance. However, as I am understanding your situation, you have list of objects of Commands, and you want particular command within those objects for given string name. For this, you must have hold of those list of instance.

      sealed trait Command {
        val typeName: String
        //This is required for implicit conversion.
        override def toString: String = typeName
      }
    
      object SendMessageCommand extends Command {
        override val typeName: String = "send_message"
      }
      object AddMessageCommand extends Command {
        override val typeName: String = "add_message"
      }
      object UpdateMessageCommand extends Command {
        override val typeName: String = "update_message"
      }
      object DeleteMessageCommand extends Command {
        override val typeName: String = "delete_message"
      }
    
      //List of commands.
      implicit val cmds: List[Command] = List(SendMessageCommand, AddMessageCommand, UpdateMessageCommand, DeleteMessageCommand)
    
      //Convert given type T into type U.
      implicit def convert[T, U](s: T)(implicit list: List[U]): Option[U] = {
        list.find(_.toString == s.toString)
      }
      val res: Option[Command] = "add_message"
      println((res.getOrElse(null) == AddMessageCommand)) //print true
    

    Note: as I am comparing two type instance in conversion by converting them into string, I have to override toString method in Command for this use case. You may need to do so for type T as well if it is not string. Furthermore, you can implement you own logic in convert method as per required instead of comparing with toString conversion.