Симметричное равенство при создании дезинфицированного строкового типа (с использованием 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