Function Declaration
func multiplyAdd(Float x, y, Int z) -> Float {
return x * y + Float(z)
}
Function declarations start with the keyword func, as in Swift.
Easier parsing due to clear distinction between function declaration vs. variable declaration, avoiding the most vexing parse.
Function parameters are given as TypeName parameterName, multiple function parameters of the (exact) same type can be combined:
func multiply(Int x, y) -> Int // x and y are Int
Always and only in the trailing return type syntax (using -> ReturnType).
Void functions (AKA “procedures”) are written without trailing :-> Void
func print(String line) { ... }
Misc
- Lambdas as in C++
[](Int i) -> Float { i * 3.1415926 } constMember Functionsclass MyArrayOfInt { const func size() -> Int { ... } }constexpr,constevalconstexpr multiply(Int x, y) -> Int { return x * y } consteval multiply(Int x, y) -> Int { return x * y }
Function pointers
Trying to maintain consistency between declarations of functions, function pointers, functors and lambdas.
Examples:
func(Int, Int -> Int)* pointerToFunctionOfIntAndIntToIntfunc(Int)* pointerToFunctionOfIntfunc(Int, Int -> Int)& referenceToFunctionOfIntAndIntToInt// Can’t be zerofunc(Int)& referenceToFunctionOfInt
Function Parameter Passing Modes
Each function parameter in Cilia has a “parameter passing mode” that defines how its argument is passed and used — whether it’s input-only, mutable, output, copied, or moved.
The basic idea is to have the most efficient/appropriate parameter passing as the default, and to give more the intent than the technical realization.
Taken from Cpp2 / Herb Sutter (surely inspired by the out parameters of C#, and by Ada).
- Default is passing as
in-parameter.- So if no parameter passing keyword is given,
inparameter passing is used. - All other parameter passing methods need to be explicitly given.
- So if no parameter passing keyword is given,
- Function call parameters are passed as either
in,inout,out,copy,move, orforward.- Wording fits nicely for function parameters: How does the parameter get into the function body (or out of it).
- The loop variable of
for ... inis passed as eitherin,inout,copy, ormove
(outandforwardare not applicable here).- With
forloops these keywords describe how the information (i.e. the variable) gets into the body of the loop (or out of it).
- With
- The argument of
catch ... { ... }is passed asin
(copy,inout,moveare not recommended,outandforwardare not applicable here).
Parameter Passing Mode Keywords
in- to mark parameters used as input.
- Is the default if no parameter passing keyword is given.
- Suitable for most basic functions, like
print(Int count, String text). - Technically either
const X&(a constant reference) orconst X(a constant copy), sometimesconst XView(a view type, e.g. a slice).const X&as default, suitable for most, medium to large types:add(BigInt a, BigInt b)- is effectively translated to
add(const BigInt& a, const BigInt& b)
- is effectively translated to
BigInt[] bigIntArray = ...
for i in bigIntArray { ... }iisconst BigInt&
const Xfor “small types” (likeInt,Float, etc.):for i in [1, 2, 3] { ... }iisconst Int
for str in ["a", "b", "c"] { ... }strisconst StringView(a string-literal like"a"forms aconst StringView, therefore["a", "b", "c"]is aconst StringView[3])
const XViewfor typesXthat have a corresponding view type:concat(String first, String second)- is effectively translated to
concat(const StringView first, const StringView second)
- is effectively translated to
String[] stringArray = ...
for str in stringArray { ... }strisconst StringView
inout- to mark parameters used as input (so they need to be initialized at the caller) and as output.
- Technically a non-const/mutable reference (
X&) - Suitable for e.g.
swap(inout Int a, inout Int b). Keywordinoutis also to be given at the caller:swap(inout a, inout b)- No, because
- it is verbose,
- it is not a reliable warning/guarantee that the argument may be changed, as any reference-like type (e.g.
Span<T>) allows change even withoutinout.
- No, because
- Examples:
for inout str in stringArray { ... }strisString&
for inout i in intArray { ... }iisInt&
out- to mark output parameters (is initialized at the callee).
- Technically, like
inout, a non-const/mutable reference (X&), but without prior initialization. - Keyword
outis also to be given at the caller:String errorDetails if not open("...", out errorDetails) { cout << errorDetails } - Maybe even with ad-hoc declaration of the out variable (as in C# 7.0):
-
if not open("...", out String errorDetails) { cout << errorDetails } -
if open("...", out String errorDetails) { // ... } else { cout << errorDetails }
-
copy- to create a (mutable) copy (i.e. pass “by value”).
- Technically a non-const/mutable value (
X), sometimes the “full class”Xof a view classXView. - Examples:
for copy i in [1, 2, 3] { ... }iis anInt
for copy str in stringArray { ... }stris aString
for copy str in ["an", "array", "of", "words"] { ... }stris aString(not a, due to theStringViewX/XView-copy-trick)
move- for move sematics.
- Technically a right-value reference (
X&&)
forward- for perfect forwarding in template functions.
- Technically written as a right-value reference (
X&&), too.
Type Trait InParameterType
The type trait InParameterType determines the concrete type to be used for in-passing.
The rule of thumb is:
- Objects that are POD (Plain Old Data, i.e. with no pointers) with a size less than or equal to the size of two
Int(i.e. up to 16 bytes on 64 bit platforms) are passed by value. - Larger objects (or non-POD) are passed by reference.
So, as general default, use const reference,
extension<type T> T {
InParameterType = const T&
}
and use a “list of exceptions” for the “const value types”.
extension Bool { InParameterType = const Bool }
extension Int8 { InParameterType = const Int8 }
extension Int16 { InParameterType = const Int16 }
extension Int32 { InParameterType = const Int32 }
extension Int64 { InParameterType = const Int64 }
extension UInt8 { InParameterType = const UInt8 }
extension UInt16 { InParameterType = const UInt16 }
extension UInt32 { InParameterType = const UInt32 }
extension UInt64 { InParameterType = const UInt64 }
extension Float32 { InParameterType = const Float32 }
extension Float64 { InParameterType = const Float64 }
extension std::string_view { InParameterType = const std::string_view }
extension std::span<type T> { InParameterType = const std::span<T> }
...
This way developers only need to extend this list if they create a small class (and if they need or want maximum performance). And I expect most custom classes to be larger than 16 bytes (so nothing to do for those).
extension<type T> Complex<T> { InParameterType = T::InParameterType }
extension Complex<Float128> { InParameterType = const Complex<Float128>& }
Accordung to the generic rule, Complex<T> is passed the same way as T. But as sizeOf(Complex<Float128>) is 32 bytes (so pass by reference is desired), despite sizeOf(Float128) is 16 bytes (so pass by value would be the default), the generic rule is corrected.
Special Trick for Types with Views
Applicable only for types X that have an XView counterpart where
Xcan implicitly be converted toXView,XViewcan (explicitly) be converted toX, andXViewhas the same “interface” asconst X(e.g. contiguous memory access).
Like:
extension String { InParameterType = const StringView }
extension Array { InParameterType = const ArrayView }
extension Vector { InParameterType = const VectorView }
So for an in String in fact a const StringView is used as parameter type.
Then all functions with a String (AKA in String) parameter would implicitly accept
- a
String(as that can implicitly be converted toStringView) - a
StringView(that somehow is the more versatile variant ofconst String&), - and therefore also every third-party string class (as long as it is implicitly convertible to
StringView).
This way people do not necessarily need to understand the concept of a StringView. They simply write String and still cover all these cases.
Example
concat(String first, String second)is short for
concat(in String first, in String second)
and extends toconcat(const StringView first, const StringView second).
Though I don’t see any advantage with respect to the for ... in loop, I would still apply the same rules just for consistency.
String[] stringArray = ["a", "b", "c"]
for str in stringArray {
// str is a const StringView
}
This is not possible with every view type, as some views do not guarantee contiguous memory access (typically when they do support stride):
Matrix-MatrixViewImage-ImageViewMDArray-MDArrayView(AKA MDSpan?)- Maybe having some
XBasicViewinstead, explicitly without stride support,
that can cut off at start and end, but no slicing:Matrix-MatrixBasicViewImage-ImageBasicViewMDArray-MDArrayBasicView
Small ...View-classes with a size of up to 16 bytes (such as StringView, ArrayView, and VectorView) will be passed by value:
extension String { InParameterType = const StringView }
extension Array { InParameterType = const ArrayView }
extension Vector { InParameterType = const VectorView }
Bigger ...View-classes with a size of more than 16 bytes (such as MatrixBasicView, ImageBasicView, and MDArrayBasicView) will be passed by reference:
extension Matrix { InParameterType = const MatrixBasicView& }
extension Image { InParameterType = const ImageBasicView& }
extension MDArray { InParameterType = const MDArrayBasicView& }
But you do not have to write that explicitly, because const& simply is the standard for user defined types anyway.
Type Trait CopyParameterType
The type trait CopyParameterType of a type T typically is simply T itself:
extension<type T> T { CopyParameterType = T }
But for View-types the corresponding “full” type is used instead:
extension StringView { CopyParameterType = String }
extension ArrayView { CopyParameterType = Array }
extension VectorView { CopyParameterType = Vector }
extension MatrixView { CopyParameterType = Matrix }
extension MatrixBasicView { CopyParameterType = Matrix }
extension ImageView { CopyParameterType = Image }
extension ImageBasicView { CopyParameterType = Image }
extension MDArrayView { CopyParameterType = MDArray }
extension MDArrayBasicView { CopyParameterType = MDArray }
The idea is to get a mutable copy of the object, even without understanding the concept of a View.
Example
for copy str in ["an", "array", "of", "words"] { ... }While the literal
["an", "array", "of", "words"]is aStringView[],stris aString(not a).StringView
This way people do not necessarily need to understand the concept of a StringView literal. They simply write copy to get a String with a copy of the content of the StringView.
(This is currently the only useful example I can think of.)