-
Notifications
You must be signed in to change notification settings - Fork 54
overload
Only functions can be overloaded; this includes both polymorphic and template functions.
Overloading of functions is accomplished with the overload
and with
keywords. We choose a string to become a symbol using symintr
,
then overload a function with this symbol. For instance, a standard
way of doing this is as follows:
symintr foo
fun foo1(x: atype): btype
overload foo with foo1
If the appropriate function cannot be resolved based on type, a precedence
mechanism can be specified by using the of
keyword followed by an (non-negative? positive?) integer.
Of course, one may want to think carefully
about using overloading in this way, since it could be easy to forget
exactly which function is being used in practice; when in doubt, use the
actual function name when calling the function. Building on the
previous example:
symintr foo
fun foo1(x: atype): btype
overload foo with foo1 of 10
fun foo2(x: atype): btype
overload foo with foo2 of 20
A higher number after of
signifies a higher priority, so all else being equal, foo2 will be used
if we have a call like foo(atype).
Whether an operator is infix or postfix can be declared using the infix
, infixl
, infixr
, prefix
, and postfix
keywords. For instance, the following changes +
to be a postfix operator, and we can overload it with
some function of our choice, say myfun(atype).
postfix +
overload + with myfun
(* Now instead of doing myfun(atype) we can do: *)
val x: atype = some_val
x+
(* But we can no longer do this in the same scope: *)
val m = 3 + 5
Some strings are reserved for other purposes by default in ATS. For instance, &&
is a macro.
However, if we do
symintr &&
We can now overload &&
with a function of our choice. Of course, the original functionality of &&
will no longer
work in the same scope.
Bracket overloading uses the pair of symbols [
and ]
to allow functions to be called using a subscripting style, where the argument to the left of [
(and the first argument of the function being overloaded) is usually thought of as the "data object" and the arguments between [
and ]
are thought of as somehow being an index or parameterizing the value on the left of the [
. That is only the convention though, with array and matrix subscripting being prominent examples; in principle it could be used in other ways.
Note the following rules:
- The last function argument of a function returning void may be used as an r-value in the overloaded operator expression, e.g.
f(x,y) -> void
// replaced by
val _ = x[] := y
- If the function returns values, these may be assigned to as l-values in the overloaded operator expression, e.g.:
f(x,y) -> z
// replaced by
val z = x[y]
Here is a matrix subscripting example, which doesn't compile, but see prelude_matrixptr.dats for full code:
(* in prelude/SATS/matrixptr.sats *)
fun{a:t0p}
matrixptr_get_at_int
{m,n:int}
(
A: !matrixptr(INV(a), m, n), i: natLt (m), n: int n, j: natLt (n)
) :<> (a) // end of [matrixptr_get_at_int]
fun{a:t0p}
matrixptr_set_at_int
{m,n:int}
(
A: !matrixptr(INV(a), m, n), i: natLt (m), n: int n, j: natLt (n), x: a
) :<!wrt> void // end of [matrixptr_set_at_int]
overload [] with matrixptr_get_at_int
overload [] with matrixptr_set_at_int
(* in doc/EXAMPLE/ATSLIB/prelude_matrixptr.dats *)
val m = 2 and n = 5
val A = (arrayptr)$arrpsz{int}(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
val M = arrayptr2matrixptr (A, m, n)
//
val () = M[0, n, 0] :=* 2
val () = M[0, n, 1] :=* 2
//...
Note an interesting point here is that each use of []
actually employs both the get and set functions, since []
is overloaded by both functions.
A somewhat less conventional example using nothing between [
and ]
is locking access to a variable. See the ATS Tutorial section on Bracket Overloading for more information.
Dot symbol overloading allows the emulation of selecting fields from a record-value or methods from an object-value, which can be quite useful when interacting with other languages that support these features. There are two forms of dot-notation that can be used for overloading in ATS: dot-notation and functional dot-notation. Functional dot-notation looks like the system used in Python, and is a slightly restricted form that must be used for linear-values. This is demonstrated below; for more information see the ATS Tutorial section on Dot-Symbol Overloading.
//
typedef counter = int
//
extern
fun counter_make (): counter
extern
fun counter_free (counter): void
//
extern
fun counter_get (cntr: !counter): int
extern
fun counter_incby (cntr: !counter, n: int): void
//
//
overload .get with counter_get
overload .incby with counter_incby
//
Instead of implementing counter as an int
, what if it were a linear value?
//
(* typedef counter = int // replace this with a linear type *)
absvtype counter = ptr
//
Now this will NOT typecheck:
val n0 = c0.get
But this will:
val n0 = c0.get() // = counter_get(c0)
The reason is that the functional style must be used for linear values, to avoid having the situation where a linear value is overwritten like this:
val () = foo.x := x0
While the above is natural to write, it is less natural to write, which of course won't typecheck:
val () = foo.x() := x0
Thus, enforcing the use of functional dot-notation for linear types makes this process natural.
A combination of macros (macdefs) and fixity assignments can be used
to introduce syntactic sugar for specialized symbols. For instance,
the example below allows ^t
to be used as a postfix operator used
to take the transpose (function transp_LAgmat) of a matrix:
(* ****** ****** *)
(*
HX: a hackery of a little fun
*)
#define t 't'
infixr ^
macdef ^ (M, t) = transp_LAgmat (,(M))
//
(* ****** ****** *)
Insert simplified version of failed example: https://groups.google.com/forum/?utm_medium=email&utm_source=footer#!msg/ats-lang-users/Y1PxLL-9jfU/kyjkxB0wwPkJ