Yes. In Haskell, types are your friends. You should define new types liberally.
I think the usual approach is to create newtype wrapper:
> module EmailAddr (EmailAddr, mkEmailAddr) where
>
> newtype EmailAddr = EmailAddr String
>
> mkEmailAddr :: String -> Maybe EmailAddr
> mkEmailAddr str = if isEmailAddr then Just (EmailAddr str) else Nothing
The only way to make an EmailAddr is via the mkEmailAddr function, which checks that the string is actually a valid address (implementation omitted). Therefore, there's a guarantee that any EmailAddr is actually a well-formed email address, and any functions that operate on an EmailAddr can rely upon this.
Of course, it might be useful to create an actual algebraic type instead:
> data EmailAddr = EmailAddr { address :: String, domain :: String }
(and the domain could similarly be an algebraic type instead of a plain string)
In general, you should be working with types that closely reflect the domain you're working in. This will make your functions more clear, and the compiler/type checker will be able to provide more help during development.
John L.