How does one express the signature for a function
that must return an argument (or this
) that it receives (is called on), in TypeScript? Is there a programming language where this is possible?*
// In TypeScript (or consider it pseudo-code)
class C {
// EXAMPLE 1 – Not polymorphic
chainable(x): this // MUST not only return some C,
{} // but the same instance it was called on
}
// EXAMPLE 2
function mutate<T>(a: T[], x): T[] // MUST return a, not a new Array
{
/* So that this doesn't compile */ return Array.from(a);
/* But this is OK */ return a;
}
Conversely, how about a function
that must return a new instance?
// EXAMPLE 3
function slice<T>(a: T[], x, y): T[] // MUST return a new Array
Would the following contract
achieve the above?
contract referentiallyIdentical(f F, p P) {
f(p) == p
v := *p
}
type returnsSameIntSlice(type T, *[]int referentiallyIdentical) T
func main() {
var mutate returnsSameIntSlice = func(a *[]int) *[]int {
b := []int{2}
/* Would this compile? */ return &b
/* This should */ return a
}
}
Could the above be expressed as a C++ concept
?
*Originally, the question was about doing this in TypeScript, but since that isn't possible, I am curious if it is in another language.
Feel free to remove a tag if that language's type system can't express this
You can - in Scala.
Class with a method returning this.type
:
class C {
var x = 0
/** Sets `x` to new value `i`, returns the same instance. */
def with_x(i: Int): this.type = {
x = i
this // must be `this`, can't be arbitrary `C`
}
}
In-place sort that guarantees to return exactly the same array (doesn't really sort anything here):
def sortInPlace[A: Ordered](arr: Array[A]): arr.type = {
/* do fancy stuff with indices etc. */
arr
}
If you attempt to return a different array,
def badSortInPlace(arr: Array[Int]): arr.type = Array(1, 2, 3) // won't compile
you'll get an error at compile time:
error: type mismatch;
found : Array[Int]
required: arr.type
def badSortInPlace(arr: Array[Int]): arr.type = Array(1, 2, 3)
^
This is called a singleton type, and is explained in the spec.
In a language with parametric polymorphism, any function of the type
a → a
must be the identity function: since the function is polymorphic in a
, it cannot possibly know anything about a
, in particular, it cannot possibly know how to construct an a
. Since it also doesn't take a world value or an IO
monad or something equivalent, it cannot get a value from global state, a database, the network, storage, or the terminal. It also cannot drop the value, since it must return an a
.
Ergo, the only thing it can do is to return the a
that was passed in.