jakt
jakt copied to clipboard
Aliasing and copying types
I feel like C++ currently has a slight problem with its type system. Whenever we use either typedef or using to declare a symbol, it’s only an alias, it isn’t treated as it’s own type. The main problem with this is function overloading. I could imagine some code akin to the following:
let Foo = SomeSuperLongTypeName<i32>
let Bar = SomeSuperLongTypeName<i32>
function do_stuff_with(foo: Foo) {
return “result of doing stuff with Foo”
}
function do_stuff_with(bar: Bar) {
return “result of doing stuff with Bar”
}
function main() {
let foo = Foo()
let bar = Bar()
let a = do_stuff_with(foo)
let b = do_stuff_with(bar)
}
If the types Foo
and Bar
were aliases and not copies of SomeSuperLongTypeName<i32>
, this code would be ambiguous. So I’d like to propose keywords for aliasing/copying types. Perhaps we could use alias
and type
where alias
would be treated like a simple name substitution and type
would copy the type definition. Alternatively maybe we could use let
for the type definition copy.
So what you want is something that works in similar way to Haskell's newtype
? Creating a new type ID but with the same underlying structure?
So what you want is something that works in similar way to Haskell's
newtype
? Creating a new type ID but with the same underlying structure?
Yes, exactly that. Chose to not propose the Haskell equivalent names (type
and newtype
) since it isn't exactly clear that type
is an alias. Also think that newtype
is kind of long for a keyword.
I'm not sure about the example problem you're proposing: overloading. If you're overloading, shouldn't the argument have different structure? I agree with the type "copying" but I would propose a different use case:
// different meaning, same underlying type!
let KeyHash = usize
let UserID = usize
function do_stuff_with_user(user: UserID) {
/* ... */
}
function do_stuff_with_key_hash(admin: AdminID) {
/* ... */
}
function get_user_id() -> String => "..."
function hash_string(anonymous str: String) -> KeyHash {
/* ... */
}
function main() {
let user_id = get_user_id()
let hash = hash_string("some-key")
do_stuff_with_key_hash(user_id)
do_stuff_with_user(hash) // now OK, better if it errored!
}
I'm not sure about the example problem you're proposing: overloading. If you're overloading, shouldn't the argument have different structure? I agree with the type "copying" but I would propose a different use case:
// different meaning, same underlying type! let KeyHash = usize let UserID = usize function do_stuff_with_user(user: UserID) { /* ... */ } function do_stuff_with_key_hash(admin: AdminID) { /* ... */ } function get_user_id() -> String => "..." function hash_string(anonymous str: String) -> KeyHash { /* ... */ } function main() { let user_id = get_user_id() let hash = hash_string("some-key") do_stuff_with_key_hash(user_id) do_stuff_with_user(hash) // now OK, better if it errored! }
This is really the same thing, but a negation. In this case I'd expect an error stating that there is no overload
function do_stuff_with_user(KeyHash)
But yes. That's an excellent example of where the compiler would silently let us do something we clearly didn't intend to.
I've done this pattern in Rust via a tuple-like struct with a single, unnamed field (which also gets inlined, and you provide the operator impls like a new type). Maybe that's better instead of adding 'type copy' or whatever, and leave type aliases to have their own syntax.