jakt icon indicating copy to clipboard operation
jakt copied to clipboard

Aliasing and copying types

Open juniorrantila opened this issue 2 years ago • 5 comments

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.

juniorrantila avatar May 26 '22 18:05 juniorrantila

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?

cg-jl avatar May 26 '22 19:05 cg-jl

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.

juniorrantila avatar May 26 '22 19:05 juniorrantila

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!
}

cg-jl avatar May 26 '22 19:05 cg-jl

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.

juniorrantila avatar May 26 '22 20:05 juniorrantila

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.

cg-jl avatar May 26 '22 21:05 cg-jl