For some language features, we want extra comments from developers before finalizing syntax and semantics.
Discussions are open on GitHub for each RFC below. You may comment and react to other comments on those threads.
Where given, consideration options (listed alphabetically) aren't exhaustive. You may request other options, or comment about or propose changes to existing ideas.
-
String literal formats
- Ideas for existing quote styles:
- Double quotes
"for multiline strings with interpolation - Single quotes
'for single-line strings without interpolation - Backticks
`for multiline strings with no interpolations or character escapes (raw strings)
- Double quotes
- New string formats (what quoting style to use?)
- Strings that can be formatted multiline in source code, but are concatenated into a single line. ("folding strings")
- Strings that allow escaping quotes??
- Ideas for existing quote styles:
-
Regex literal formats
- Should regex literals be
#/.../or%/.../? - How can regex literals be defined with multiple-
/? (to allow including/without escaping)
- Should regex literals be
-
Resource management
- Implementations in other languages:
- Python:
withblocks - Gleam:
usedeclarations that can be set to functions that take a callback. - JavaScript:
usingdeclarations that can be set to objects with a dispose method - Java:
tryblocks (Try-with-Resources)
- Python:
- Should the keyword be
useorusing?
- Implementations in other languages:
-
Should it be called
nilornone? -
Should methods be allowed on interfaces? - These would allow interfaces to contain methods that use interface values as receivers/
self.type #MyInterface { key: Int } func MyInterface.method() { print(self.key) } impl: MyInterface := someObject impl.method() -
Publicity for struct fields/methods
- a. Private by default
- b. Private fields should be prefixed with
_ - c. Add a new
privatekeyword. Downside: Another keyword
For methods, which are declared in the same block as module-level functions:
- a. should the
publicbe used (for consistency with module-level functions), or - b. methods are public by default, and
_should be used to make them private?
public func Struct.method() func Struct._method() public func moduleFunc() -
How to distinguish variable values from unwrapped variable declarations in
whenpattern matchingExample:
val := "foo" when value { // Is val declared as a variable set to the first field in MyStruct, or this checks whether the first field's value is "foo"? MyStruct(val) -> { ... } // Is val a type, or "foo"? val -> { ... } }Current approach:
valas a direct case (2nd) will compare to a variable, and unwrappingvalinMyStruct(1st) is a variable. -
Placement of pipeline values in function calls: Unless the
valuevariable is used in a step:- a. First parameter
- b. Last parameter
- c. Require the use of
valuefor functions that take multiple parameters
val := 'foo' func bar(x: String, y: Int) {} func reverse(str: String) -> String {} // Option A val |> reverse |> bar(2) // With Option C, this is required val |> reverse |> bar(value, 2) -
Private modules
- a.
internalfolder that can't be imported by packages outside the project - b. Folders prefixed with
_ - c. Don't enforce module privacy, but also don't guarantee backwards compatibility when importing objects from them. While backwards compatibility will be tied with semantic versioning for packages, this won't be enforced for internal modules.
- a.
-
Should bitmask enums be added? These would allow for multiple enum items to be applied as a single value, with efficient O(1) item detection. Algebraic enums or items with parameters will be allowed.
-
Example of bitmask types in Go:
type MyEnum uint8 const ( Option1 MyEnum = 1 << iota Option2 Option3 ) var myValue MyEnum = Option1 | Option3 // Check for an option (myValue & Option3) != 0 // Assign an option myValue |= Option2 // Remove an option myValue &^= Option1 // JavaScript: myValue &= ~Option1
-
For Klar:
@flag // or '@flags' type MyEnum { .option1 .option2 .option3 } myValue := MyEnum(.option1, .option3) myOpts: [MyEnum] := [.option1, .option3] myValue := MyEnum(myOpts...) // Possible approaches for checking myValue.has(.option3) (.option3 in myValue) // Possible approaches for setting myValue.set(.option2, true) // Mutating myValue.set(.option2) // `true` as a default param myValue = myValue.with(.option2) // Non-mutating myValue = MyEnum(myValue..., .option2) myValue += .option2 // Possible approaches for removing myValue.set(.option1, false) // Mutating myValue = myValue.without(.option1, false) // Non-mutating myValue -= .option1 -
To consider: If items with parameters are allowed, how can they be checked?
-
-
Iterators
- a. New
Iterator<T>type (possibly builtin). Downsides- Another (builtin) type with niche applications that beginners still have to learn.
- Iterators can yield 1 or 2 items. How will that be implemented in the type signature? Have a separate
Iterator2<A, B>type, similar to Go, and be required to duplicate methods fromIterator? OrIterator<T>andIterator<(A, B)>
- b. Iterators are just lists, but optimized by the compiler when used in for-loops (preferred, but future work). A function can return a list, and if it is created and iterated over using specific operations, the compiler can optimize it.
- c. Add generator functions that can yield values to the language
- a. New
-
Should the compiler enforce identifier capitalization conventions? (
PascalCasefor types,camelCasefor variables). Nosnake_casefor variables. -
Disallow tuples with 0 or 1 items? Currently, to create a tuple with 1 item, you have to add a trailing comma
(1,). -
How should leaking
Tasks be handled by the compiler?- a. Require the function spawning the task to be called with
go - b. Require all tasks to be awaited
- a. Require the function spawning the task to be called with
-
Chaining assertions - to assert both the
Resultand optional in theResult<Int?>type:- a. Does
!!have to be used for each (twice), or - b.
!!asserts both theResultand the optional (asserts all ->Int)
- a. Does
-
Readonly struct fields - What keyword should be used?
readonly? But the field isn't actually readonly. It can still be modified internally.For now,
readonlywill be used to refer to this feature in RFCs.
-
Readonly interface fields
- a. Add a
writable/writekeyword that can be applied on fields, similar toreadonlyon structs. The downside is that is another keyword, when we already havereadonly. - b. All fields are writable by default, and
readonlycan be used to override. - c. Interface fields can't be modified at all. Mutation requires a setter (e.g.
setFoo()). Note that requiring fields to be writable makes the interface harder to implement. If a struct has a fieldreadonly foo, it can't implement any interface that requiresfoo. Option B implicitly makes this too restrictive.
- a. Add a
-
Should lists and tuples use 1-based indexing?
- 0-based indexing is a convention taken from C, where array indexing is equivalent to taking memory offsets. We are careful about borrowing semantics from C into Klar, especially the low-level ones.
- Using 1-based indexing allows the list's
lengthto be used as a valid index, avoiding off-by-one bugs. - Lua, Julia, MATLAB, R, and Fortran are examples of languages with 1-based indexing.
- Using 1-based indexing means the
..<operator will be removed, so only...is needed. - The only downside is JavaScript interopability. When compiling to JavaScript, a subtraction operation is needed for any array index operation; and may break compatibility with JavaScript interfaces that implement Klar interfaces and contain indexing methods.
-
Errors
- My goal is for error objects to have codes attached. My preferred way is one defined by the module creating the error, which may be an enum.
// Module myMod public type ErrorCode { .notFound(path: String) .accessDenied } // A user of 'myMod' could check like: when err.code { .notFound(path) -> ... .accessDenied -> ... }- a. Make
Erroran interface, similar to Go, with the message as a String field, and a code (Anytype). Also, should they be fields, or methods (so structs can compute messages; but that could also be done by an initializer)? - b. Make
Errora structtype Error { message: String code: Any }
-
Fallable list indexing - Should all string/list index/slice operations return
Results, or crash when out of range? -
Resultmethods: Should these methods be added to theResulttype?type Result<T, E> func Result.isSuccess() -> Bool ok := myResult.isSuccess() // Without (idiomatic and consistent): ok := when myResult { Error -> false _ -> true } func Result.else<U>(errValue: U) -> T | U value := myResult.else('default value') // Could also be called 'Result.or()' // Without (also idiomadic and consistent): value := when myResult { Error -> 'default value' _ -> myResult } func Result.mapSuccess<U>(to successValue: U) -> Result<U, E> // Could also be called 'Result.and()' func Result.mapSuccess<U>(with mapper: func(successValue: T) -> U) -> Result<U, E> func Result.mapError<U>(to errValue: U) -> Result<T, U> func Result.mapError<U>(with mapper: func(errValue: E) -> U) -> Result<T, U> func Result.swap() -> Result<E, T> // Could also be called 'Result.invert()' func Result.takeSuccess() -> T? func Result.takeError() -> E? -
Should
TaskandRegexbe builtin types?- a. Builtin types (either or both) (that beginners have to be introduced to)
- b. They should be imported from the standard library (ex.
concurrency.Taskandregex.Regex). The downside is they have native syntax in the language, but to annotate, they have to be imported.
-
Should the exponentiation
^operator be left- or right-associative? In Python and JavaScript, exponentiation is right-associative, so2 ** 3 ** 2(they both use**for exponentiation) =2 ** (3 ** 2)=512. For Klar, using right-association may be confusing and ambiguous. -
Struct inheritance - In Klar, we want inheritance to be solely based on copying fields and methods, rather than full OOP. There will not be a
super()constructor that must be called. The first idea that comes to mind is copying fields from other types, and if there is a field collision between 2 inherited types, it must be explicitly overriden.type A { x: Int } func A.hasX() = self.x != 0 type B { x: String } func B.hasX() = self.x != "" type MyStruct: A, B { // Since there is a collision between field 'x', it must be explictly declared here x: Float }There are a few concerns with this:
- In the declaration, it says type
MyStructinheritsAandB, but when we override a field's type,MyStructcannot be converted to either type. This is misleading. - There will be no method
hasXonMyStructbecause the methods on both inherited types reference the fieldxof a different type. With these rules, it may not be clear what methodsMyStructinherits (finding out may require looking in the bodies of several methods in the inherited types, which may also have their own inherited types). - In structs, field declarations can refer to other fields, such as
type X { x: Int, y: Int = self.x }. A type override could remove other fields.
I would like some ideas on how inheritance can be implemented, such as Go-style embedding.
- In the declaration, it says type