diff --git a/.vitepress/config.mts b/.vitepress/config.mts index a97f4a6..8391d5e 100644 --- a/.vitepress/config.mts +++ b/.vitepress/config.mts @@ -235,6 +235,14 @@ export default defineConfig({ }, ], }, + { + text: 'Responsiveness', + link: '/responsiveness/', + items: [ + { text: 'Mutabilitiy', link: '/responsiveness/mutability' }, + { text: 'Expressions', link: '/responsiveness/expressions' }, + ], + }, { text: 'Memory', link: '/memory/', diff --git a/src/memory/immutability.md b/src/memory/immutability.md index 24f9799..2d9e46c 100644 --- a/src/memory/immutability.md +++ b/src/memory/immutability.md @@ -24,53 +24,4 @@ Okay, so why doesn't the compiler implement immutability by default only for tho Traits are considered mutable types because the type of data they store can be mutable. It is not possible to track this at runtime. Because, in its simplest explanation, an immutable trait data can be assigned to a mutable memory. However, it is not possible to understand at compile time whether the trait stores mutable data, so it's risky. The only way to check this is to add an additional unwanted runtime. -However, there is one point for traits: they respect the concept of interior mutability. If the data stored by a trait implements interior mutability, this is allowed. - -## Responsive Immutability - -Responsive immutability is the state of protection when immutability is guaranteed. For example, if a slice is guaranteed to have an immutable memory area at the time it is created, it is allowed to have mutable types stored in the immutable memory area, even though it is a mutable type. - -For example: -```jule -struct Test { - slice: []int -} - -fn main() { - let x = [1, 2, 3, 4, 5] - let s = Test{ - slice: x, - } - println(s) -} -``` - -In the example above, the variable `x` is an immutable slice. However, when a mutable field is assigned, it becomes possible to change immutable data because it is mutable. This is clearly a violation of immutability. However, it is guaranteed that the `Test` structure created for the `s` variable will be immutable because the `s` variable to which it will be assigned is immutable. Your compiler will be fine with this. However, if immutability is not guaranteed or the compiler cannot understand it, you will be explicitly warned about mutability. - -In some cases, certain conditions may cause loss of responsiveness. For example, you create a struct literal and assign it to immutable memory. The structure will be considered immutable, but you cannot avoid the risk of interior mutability. If the field of ​​the struct you assign is within the scope of interior mutability, mutability control is still performed for that field. - -Here is some code that causes this issue: -```jule -struct Foo { - mut s: []int -} - -fn main() { - let x = [1, 2, 3, 4] - let y = Foo{ - s: x // Interior mutability, risk. - } - _ = y -} -``` - -In the code above, the `s` field of the `Foo` structure poses a risk since it is within the scope of interior mutability. However, since there is no method that can be changed within the scope of interior mutability, the compiler will allow this. If the structure has any method, it will assume there is a risk of mutability, regardless of whether interior mutability is used. - -Let's implement such a method for the `Foo` struct: -```jule -impl Foo { - fn bar(self) {} -} -``` - -In this case, the structure has a method, even if it is empty. In order not to increase compilation costs too much, Jule does not examine this and considers it risky. In this case, your compiler will check the mutability risk for the `s` field that is within the scope of interior mutability and complain if necessary. \ No newline at end of file +However, there is one point for traits: they respect the concept of interior mutability. If the data stored by a trait implements interior mutability, this is allowed. \ No newline at end of file diff --git a/src/memory/mutability.md b/src/memory/mutability.md index 49b7ab7..0e4487f 100644 --- a/src/memory/mutability.md +++ b/src/memory/mutability.md @@ -58,7 +58,7 @@ For example: ```jule struct MyStruct { mut x: int - y: int + y: int } ``` diff --git a/src/responsiveness/expressions.md b/src/responsiveness/expressions.md new file mode 100644 index 0000000..cd5bc34 --- /dev/null +++ b/src/responsiveness/expressions.md @@ -0,0 +1,105 @@ +# Responsive Expressions + +Responsiveness of expressions is based on eliminating the need for some additional information whenever possible. This is something that is largely based on the static type system. If your compiler knows the type in which the expression will be used, it can provide some optional simple methods like removing static types. + +Responsive identification of types is nothing new. Some languages ​​have even paid special attention to this in their design. + +For example, from the readme of the Crystal programming language GitHub repository: +> We want the compiler to understand what we mean without having to specify types everywhere. \ +> _**quote commit**: `991f9d0`_ + +Of course, this isn't a main focus of Jule's design, but it's considered reasonable to have it to a certain extent. + +## Untyped Constant Numerics + +Constant numeric expressions are flexible. Numeric values that are untyped (constant numerics that do not have any explicit type) are automatically handled as appropriate for the target type, if they are compatible with the target type. + +For example: +```jule +fn main() { + const a = -98345 + let b: i32 = a + println(b) +} +``` +The above code will compile successfully. The constant value `a` does not have an explicit type, so it is treated as untyped. The variable `b` expects a value of type `i32`. Since the expression `-98345` fits into this type, your compiler will automatically evaluate it. The same behavior will be observed with other fitter species. However, you will receive a compiler error for types that cannot take this value, such as `i16` or `u32`. + +## Slices + +Slices are one of the best examples of these responsive expressions. Because a slice literal is mostly in this scope. + +When creating a slice literal, if there is no information that it is for any type, your compiler will use the type of the first element. Since slice will always accept a single type as an element, the first element always determines the type of the slice. In this way, writing slice literals can become simpler. + +For example, writing an integer slice literal: +```jule +x := [1, 2, 3, 4, 5] +``` +In the example above, the type of the variable `x` will be `[]int` because the type of the expression will be determined this way. + +When creating a slice literal, the type for an empty literal must always be known, and if the type is known, the values ​​the literal takes are checked one by one against the target type. + +For example: +```jule +fn main() { + let mut x: []int + x = [] + x = [false, 2, 3, "foo"] +} +``` +In the example above, the empty slice literal is valid. Because the compiler knows that the expression will be assigned to the variable `x`. For this reason, the empty literal does not need an element to understand which slice type it is, it can detect its type as `[]int` in a responsive way. + +In the following assignment, the values ​​`false` and `"foo"` will produce an error. This is because the compiler expects slice literal elements to be `int`, knowing that the type of slice must be `[]int`. So your compiler won't assume that this is a bool slice literal since the first value is bool in this case, it assumes the expected type. + +## Arrays + +Arrays are like slices. There is not much difference. It has a very similar syntax and the rules are the same. There are only some differences. Array and slice literals are declared the same way. By default your compiler tends to treat it as a slice literal. Therefore array literals must always be explicitly created for an array. So it's easy for your compiler to know what the literal type is. + +Array literal elements, just like slices, are checked according to target type and can be empty. + +For example: +```jule +fn main() { + let mut x: [5]int + x = [] + x = [1, 2, 3, 4, 5] +} +``` +The above code is valid and compiles. Since literals are evaluated for assignment to the variable `x`, your compiler will consider their type as `[5]int` and checks type safety of the elements accordingly. + +## Structs + +Structs must always have an explicit type. When this happens, the name of the structure does not need to be written. You can create a structure literal using only brace. Otherwise, your compiler will require the type to be specified explicitly because it doesn't know the type. + +For example: +```jule +struct Foo { + bar: int + baz: str +} + +fn main() { + mut f := Foo{bar: 5, baz: "hello"} + f = {bar: 6, baz: "world!"} + println(f) +} +``` +In the example above, the `f` variable is first initialized with a `Foo` literal. The compiler does not have the opportunity to apply any responsiveness here because `f` is declared for the first time and its type is ambiguous. Therefore, it is clearly stated that the literal is for the `Foo` structure. However, in the subsequent assignment, there are only braces. Because the compiler now knows the type of the variable `f` and there is no need to explicitly state that it is a `Foo` structure. The compiler will assume the expected type and the literal will be treated as a structure literal for the `Foo` struct. + +## Maps + +Maps are similar to structures. When the type is clearly known, braces alone will suffice. Your compiler assumes the type will be the expected type and treats it as a literal for the corresponding map type. + +For example: +```jule +fn main() { + let mut f: map[int]str + f = { + 0: "hello", + 1: " ", + 2: "world", + 3: "!", + } + println(f) +} +``` +In the example above, since the type of the variable `f` is clearly known, it is sufficient to use only braces in the assignment. Brace literal evaluates to type `map[int]str`. \ No newline at end of file diff --git a/src/responsiveness/index.md b/src/responsiveness/index.md new file mode 100644 index 0000000..1c42076 --- /dev/null +++ b/src/responsiveness/index.md @@ -0,0 +1,9 @@ +# Responsiveness + +Jule tries to be as responsive as possible. This is the responsibility of the compiler. This intuition may arise at different points in different topics such as mutability or expressions. + +The base reason for such responsiveness is to further increase simplicity. Of course, without creating uncertainties or making things more difficult. Code that looks simple may not always be simple to read, so it needs to be balanced. + +Considering this balance, Jule tries to be as intuitive as possible. This intuitiveness may have different advantages; It could be shorter code, more flexible mutability or simplicity. + +This section documents where responsiveness is available and how. \ No newline at end of file diff --git a/src/responsiveness/mutability.md b/src/responsiveness/mutability.md new file mode 100644 index 0000000..29b1222 --- /dev/null +++ b/src/responsiveness/mutability.md @@ -0,0 +1,48 @@ +# Responsive Mutability + +Responsive mutability is the state of protection immutability when immutability is guaranteed. For example, if a slice is guaranteed to have an immutable memory area at the time it is created, it is allowed to have mutable types stored in the immutable memory area, even though it is a mutable type. + +For example: +```jule +struct Test { + slice: []int +} + +fn main() { + x := [1, 2, 3, 4, 5] + s := Test{ + slice: x, + } + println(s) +} +``` + +In the example above, the variable `x` is an immutable slice. However, when a mutable field is assigned, it becomes possible to change immutable data because it is mutable. This is clearly a violation of immutability. However, it is guaranteed that the `Test` structure created for the `s` variable will be immutable because the `s` variable to which it will be assigned is immutable. Your compiler will be fine with this. However, if immutability is not guaranteed or the compiler cannot understand it, you will be explicitly warned about mutability. + +In some cases, certain conditions may cause loss of responsiveness. For example, you create a struct literal and assign it to immutable memory. The structure will be considered immutable, but you cannot avoid the risk of interior mutability. If the field of ​​the struct you assign is within the scope of interior mutability, mutability control is still performed for that field. + +Here is some code that causes this issue: +```jule +struct Foo { + mut s: []int +} + +fn main() { + x := [1, 2, 3, 4] + y := Foo{ + s: x // Interior mutability, risk. + } + _ = y +} +``` + +In the code above, the `s` field of the `Foo` structure poses a risk since it is within the scope of interior mutability. However, since there is no method that can be changed within the scope of interior mutability, the compiler will allow this. If the structure has any method, it will assume there is a risk of mutability, regardless of whether interior mutability is used. + +Let's implement such a method for the `Foo` struct: +```jule +impl Foo { + fn bar(self) {} +} +``` + +In this case, the structure has a method, even if it is empty. In order not to increase compilation costs too much, Jule does not examine this and considers it risky. In this case, your compiler will check the mutability risk for the `s` field that is within the scope of interior mutability and complain if necessary. \ No newline at end of file