logoalt Hacker News

lock1today at 4:03 AM0 repliesview on HN

  > Because in those examples you can have a User whose email property is a ParseError and you still end up having to check "is the email property result for this user type Email or type ParseError?"
In languages with a strong type system, `User` should hold `email: Option<ValidatedEmail>`. This will reject erroneous attempts `user.email = Email::parse(raw_string);` at compile time, as `Result<Email,ParseError>` is not compatible / assignable to `Option<ValidatedEmail>`.

It's kind of a "oh I forgot to check `email.isValidated`" reminder, except now being presented as an incompatible type assignment and at compile-time. Borrowing Rust's syntax, the type error can be solved with

  user.email = Email::parse(raw_string)
      .ok()
      .and_then(|wellformed_email| {
          email_service.validate_by_send_email(wellformed_email)
      });
Which more or less gets translated as "Check email well-formedness of this raw string. If it's well-formed, try to send a test email. In case of any failure during parsing or test email, leave the `user.email` field to be empty (represented with `Option::None`)".

  > and it's very similar to just checking a validation bool except it's hiding what's actually going on.
Arguably, it's the other way around. Looking back at `email: Option<ValidatedEmail>`, it's visible at compile-time `User` demands "checking validation bool", violate this and you will get a compile-time error.

On the other hand, the usual approach of assigning raw string directly doesn't say anything at all about its contract, hiding the contract of `user.email` must be a well-formed, contactable email. Not only it's possible to assign arbitrary malformed "email" string, remembering to check `email.isValidated` is also programmer due diligence, forget once and now there's a bug.