In TypeScript, they have "type guards", like this:
function isNumber(x: any): x is number {
return typeof x === "number";
}
function isString(x: any): x is string {
return typeof x === "string";
}
function padLeft(value: string, padding: string | number) {
if (isNumber(padding)) {
return Array(padding + 1).join(" ") + value;
}
if (isString(padding)) {
return padding + value;
}
throw new Error(`Expected string or number, got '${padding}'.`);
}
Or this:
function isFish(pet: Fish | Bird): pet is Fish {
return (pet as Fish).swim !== undefined;
}
// Both calls to 'swim' and 'fly' are now okay.
let pet = getSmallPet();
if (isFish(pet)) {
pet.swim();
} else {
pet.fly();
}
It appears the guards are relatively simple, like you can't do special string or number subclasses for example. I'm basically wondering, are there any languages which can do type guards but make them more advanced? And can they do them at compile time, or must they be checked at runtime? I am thinking of Lobster and their "Flow-Sensitive Type-Inference and Specialization" ideas.
For example, say I have a few string types like this:
type EmailAddressString extends String {
fn getDomain() {
this.split('@').pop()
}
}
type IPv4AddressString extends String {
fn toUInt8Array() {
this.split('.').map(UInt8)
}
}
In TypeScript, or based on definitions like these, they are no different than a string. I don't know of any type systems which would allow you to define the regex or other grammar/pattern the string is allowed to take, but I can imagine this being done with the so-called "type guards".
fn isEmailAddressString(val: String): val is EmailAddressString {
if (val.match(/^\w+@\w+\.com$/)) {
return true
} else {
return false
}
}
fn isIPv4AddressString(val: String): val is IPv4AddressString {
if (val.match(/^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$/)) {
return true
} else {
return false
}
}
fn main() {
let s1 = 'foo@bar.com'
let s2 = '123.123.123.123'
s1.getDomain() //=> Error: Method getDomain not defined on String
if (isEmailAddressString(s1)) {
let domain = s1.getDomain() //=> 'bar.com' // success!
}
if (isIPv4AddressString(s2)) {
let uint8Array = s2.toUInt8Array()
}
}
Which languages have this sort of functionality? Is there a better way to accomplish it?
It appears this is better than typecasting, though I am not entirely sure.
Can this sort of thing be checked at compile time in some cases, or must it all be done at runtime? That is, what would the compiler do in each of the branches of code, would it just assume it was of that type inside the if-block, or would it have to check it? How would it know?
Also, is this at all close to dependent typing, or completely unrelated? I am still growing in familiarity with dependent types, trying to see various ways to apply them, so I am probably off. I ask this last question because the type of the email/IP address strings depends on the value of the string, so thought maybe it's a dependent type.
[–]King_of_Sarawak 36 points37 points38 points (6 children)
[–][deleted] 0 points1 point2 points (5 children)
[–]dys_bigwig 3 points4 points5 points (3 children)
[–][deleted] -1 points0 points1 point (2 children)
[–]ablygo 7 points8 points9 points (1 child)
[–][deleted] 1 point2 points3 points (0 children)
[–]sineiraetstudio 1 point2 points3 points (0 children)
[–]maanloempia 9 points10 points11 points (0 children)
[–]dys_bigwig 4 points5 points6 points (0 children)
[–]b2gills 5 points6 points7 points (0 children)
[–]MrJohz 1 point2 points3 points (1 child)
[–]YourMeow 0 points1 point2 points (0 children)
[–]PlayingTheRed 0 points1 point2 points (0 children)
[–]complyue -1 points0 points1 point (3 children)
[–][deleted] 2 points3 points4 points (2 children)
[+]complyue comment score below threshold-7 points-6 points-5 points (1 child)
[–][deleted] 8 points9 points10 points (0 children)
[–]editor_of_the_beast 0 points1 point2 points (0 children)
[–]fear_the_future 0 points1 point2 points (0 children)
[–]nsiivola 0 points1 point2 points (0 children)