Симметричное равенство при создании дезинфицированного строкового типа (с использованием scala.Proxy)

У меня есть приложение scala (2.10.4), где адреса электронной почты передаются по большому счету, и я хотел бы реализовать абстракцию, вызываемую в IO, для "дезинфекции" уже проверенных адресов электронной почты.

Использование scala.Proxy - это почти то, что я хочу, но я сталкиваюсь с проблемами с асимметричным равенством.

    class SanitizedEmailAddress(s: String) extends Proxy with Ordered[SanitizedEmailAddress] {
  val self: String = s.toLowerCase.trim

  def compare(that: SanitizedEmailAddress) = self compareTo that.self
}

object SanitizedEmailAddress {
  def apply(s: String) = new SanitizedEmailAddress(s)
  implicit def sanitize(s: String): SanitizedEmailAddress = new SanitizedEmailAddress(s)
  implicit def underlying(e: SanitizedEmailAddress): String = e.self
}

Я хотел бы иметь

val sanitizedEmail = SanitizedEmailAddress("[email protected]")
val expected = "[email protected]"
assert(sanitizedEmail == expected) // => true
assert(expected == sanitizedEmail) // => true, but this currently returns false :(

Или что-то с аналогичной функциональностью. Есть ли какой-нибудь не громоздкий способ сделать это?

    assert(sanitizedEmail.self == expected) // => true (but pretty bad, and someone will forget)
// can have a custom equality method and use the "pimp-my-lib" pattern on strings, but then we have to remember to use that method every time

Спасибо за вашу помощь.

Ответ 1

Я не думаю, что это возможно, извините.

Я не уверен, что это тоже правильно. Если a String действительно равно a SanitizedEmailAddress, то что на самом деле обозначает обертка SanitizedEmailAddress?

Я думаю, что было бы более непротиворечивым иметь String, не сравнимый с SanitizedEmailAddress, и требовать от пользователей "дезинфекции" ввода перед его сравнением.

Ответ 2

Вы хотите использовать маркерный признак для обозначения строк, которые соответствуют.

Функция, которая принимает строку Email, знает, что строка верна.

Если обычная строка сравнивается с строкой Email, она также должна быть правильной.

package object email {
  type Tagged[U] = { type Tag = U }
  type @@[T, U] = T with Tagged[U]
  def smartly[A](s: String): String @@ A = Email(s).asInstanceOf[String @@ A]
}

package email {
  trait Email extends Any
  object Email {
    def apply(s: String) = s.toLowerCase.trim
  }
  object Test extends App {
    def f(ok: String @@ Email) = {
      assert(ok.forall(c => !c.isLetter || c.isLower))
    }
    val x = smartly[Email]("[email protected]")

    println(x)
    f(x)
    assert("[email protected]" == x)
/*
    f("[email protected]")  // DNC
email.scala:22: error: type mismatch;
 found   : String("[email protected]")
 required: [email protected]@[String,email.Email]
    (which expands to)  String with AnyRef{type Tag = email.Email}
    f("[email protected]")
      ^
one error found
*/
  }
} 

Ответ 3

Как насчет того, чтобы сохранить все типы электронной почты в виде строки и сутенерствовать "дезинфицированную" информацию неявно:

object SanitizedEmailAddress {
  def apply(s: String): String = synchronized {
    val verified = s.toLowerCase.trim
    sanitized.update(verified, true)
    verified
  }

  def isSanitized(s: String): Boolean = synchronized {
    sanitized.contains(s)
  }

  private val sanitized = scala.collection.mutable.WeakHashMap.empty[String, Boolean]
}

implicit class emailOps(val email: String) extends AnyVal {
  def isSanitized: Boolean = SanitizedEmailAddress.isSanitized(email)
}

И теперь:

val sanitizedEmail = SanitizedEmailAddress("[email protected]")
val expected = "[email protected]"
assert(sanitizedEmail == expected) // => true
assert(expected == sanitizedEmail) // => true

assert(sanitizedEmail.isSanitized == true) // => true
assert("[email protected]".isSanitized == true) // => true
assert("[email protected]".isSanitized == false) // => true