Skip to content

Conversation

@ladaposamuel
Copy link
Collaborator

No description provided.

Comment on lines +26 to +30

```
let name = "Okechukwu"
```

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with using let for declaration of constant variables. Though const is explicit it is used in so many languages and it a bit verbose even though it just one character extra of let.

Copy link

@appcypher appcypher Nov 14, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

const has two extra characters. And I find it aesthetically inconsistent with the three-letter var.

var name = "John"
const date = "20-11-2019"
var make = "Camaro"

vs

var name = "John"
let date = "20-11-2019"
var make = "Camaro"

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair enough.... However i'd say const is more descriptive. We can debate on more points later though

Comment on lines +50 to +55
#### Shadowing
- A subject can be rebound to a different type within the same scope.
```js
let phone = "08074789222" // `phone` has type `String` here
let phone = 08074789222 // `phone` now has type `Int`
```

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There should be some restrictions on variable shadowing, a constant variable should not be shadowable by a new variable.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you explain your thoughts here? Is there a reason shadowing constants doesn't make sense?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with the @Thecarisma . This kind of shadowing can be done for mutable variables only. Then the implementation of this will require the modification of the AST in a multi-pass sequence or something.

Copy link

@appcypher appcypher Nov 14, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No one has given any compelling reason though.

I would like to know if shadowable constant can make compiler implementation hard or if it is going to lead to some unwanted consequences or sth else?

Constructive feedback is important.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shadowing constant will break the main concept of a constant itself, a constant should only be defined once.

Also for the compiler implementation, we will have an extra rule of check on a variable that shadows a constant to ensure the scope is different instead of just breaking the process.

Also, it ok with me for an inner function to shadow a constant in the parameter but not in the body of the function.

Comment on lines +147 to +164
#### Signed Integers
- Int - Pointer-sized signed integer
- I8 - 8-bit signed integer
- I16 - 16-bit signed integer
- I32 - 32-bit signed integer
- I64 - 64-bit signed integer
- Int - Pointer-sized integer

#### Unsigned Integers
- UInt - Pointer-sized unsigned integer
- U8 - 8-bit unsigned integer
- U16 - 16-bit unsigned integer
- U32 - 32-bit unsigned integer
- U64 - 64-bit unsigned integer

#### Floating point numbers
- F32 - 32-bit IEEE 754-2019 floating point representation
- F64 - 64-bit IEEE 754-2019 floating point representation

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am in absolute support of having the primitive types of different bit sizes, other types can be created from the primitive type.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is very plausible and i agree with this totally!

Comment on lines +182 to +250
## TUPLES
- Tuple is a special datatype with statically-known length. It allows for cool features like arguments unpacking.

```swift
var fave_game = ("Uncharted 4", 2016)
```

One-element tuple. Note the trailing comma.

```rust
let map = (london,)
```

Empty tuple.

```swift
let empty_tuple = ()
```

- Tuple indexing. Tuple index space starts at index 0.

```rust
let book = ("Harry Potter", "J.K. Rowling", 1995)
let title = book.1 // Harry Potter
let author = book.2 // J.K. Rowling
let year = book.3 // 1995
```

- Tuple destructuring.

```rust
let person = ("Sam", 50)
let (name, age) = person
```

- Tuple unpacking.

```swift
func add(a, b) {
a + b
}

let arguments = (5, 6)
add(...arguments)
```

- Tuples have limitations because they are expected to be amenable to static analysis.

```swift
var tup1 = (1, 2)
var tup2 = (5, 6)
```

Directly or indirectly, a tuple can't be used to extend itself.

```go
for i in range(x) {
tup2 = (...tup2, ...tup1) // Invalid!
}

for i in range(x) {
tup3 = (...tup2, 1)
tup2 = (tup3, tup1) // Invalid!
}
```

- The type of a tuple.

Since tuples can't change in size, a tuple has a fixed type. For example the tuple `(0xFF, "Hi")` has type `(Int, String)`. `("Hello", 5.0, "Hi")` has type `(String, F64, String)`

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not support the idea of tuple, I believe a tuple is simple an immutable list just that tuple are created with bracket.

We can have a tuple as a high-level type that accepts variadic and any type as the parameter which uses methods and function instead of built-in language feature. e.g as a function

var fave_game = tupple("Uncharted 4", 2016)

as a type

var fave_game = Tupple::new("Uncharted 4", 2016)

All the rules guiding a proper tuple can be put in place on the high-level Tuple type.
The idea of unpacking a tuple is great as I find it the ultimate usefulness of a tuple so we can add the feature to list i.e a list can be unpacked for use as parameters.

Copy link

@appcypher appcypher Nov 13, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well tuple isn't just an immutable list here. Immutable list is easily implemented in the language.

The reason tuple has to be special here is because it has compile-time properties that makes it useful for certain scenarios.

1.) It has a length known at compile-time.
2.) It has element types known at compile-time.

And those are the reasons we can't just make tuple an immutable list. List length, immutable or not, are not always statically known.

let length: Int = input_from_user()
let values = List::new(length)

In the example above, we don't know the list length at compile-time, because length is only provided at runtime.

Also, for a heterogenous list, the individual element types are not always statically known.

let samples: [String|Int] = []

for i in 0..len {
    samples.push(
         if cond { "Hi" } else { 20 }
    )
}

let values: [String|Int] = samples

samples can contain both String and Int elements, but it is very possible that at the end of the loop, there is no item in samples; that samples contain only String items; that samples contain only Int items; that samples contain items of both types in any order.

let samples: [String|Int] = []

for i in 0..len {
    samples.push(
         if cond { "Hi" } else { 20 }
    )
}

let values: [String|Int] = samples

func show(a: Int, b: Str) -> {
    print(, b.upper())
}

show(...values) # Invalid! 

We can't unpack values here because values can have any length here, and its elements can be of any type. But we need those information at compile-time to correctly type-check the function. This is why immutable list is unsuitable for this.

On the other hand, a tuple has this information available at compile-time. The individual element types and the length of the tuple are known at compile time.

But being able to unpack a tuple as function arguments isn't the only benefit of here. Tuples are more efficient than Lists memory-wise and perf-wise (reduces cache misses). Tuples are like structs, except indexable by integer and suitable for unpacking.

This is how Rust and Swift tuples work. And this is also how C++ tuples work, although the tuple is implemented behind a variadic template type.

Comment on lines +305 to +316
## RANGE
- Range is an iterable that allows iterating through a range of values.

```swift
let days = 1..31 // 1 to 30
```

- A range can have a step value.

```swift
let evens = 2..2..20 // The step value is the `2` in the middle.
```

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For range between values we should use the colon operator to indicate the range e.g

let days = 1:31

and a range with step value

let events = 2:2:20

For the step, it easily understandable to have the step value at the end of the range expression.

let events = 2:20:2 //the 2 at the end is the step value

Copy link

@appcypher appcypher Nov 14, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This conflicts with the generics application syntax.

func new[T]() -> Self { 
    // ...
}

let buf = Buffer::new:[Int]() // Generics application

The reason I chose this generics syntax over the typical Buffer::new<Int>() syntax, is that it keeps our grammar in the CFG zone. C++ parser is complicated for a reason and it is because it is context-aware which sucks. It makes parser really hard to implement, and language grammar impossible to fully specify with EBNF, PEG and what not.

Here is why C++ template syntax is context aware. It's easy for the parser to confuse a '<' as a lesser than operator, so it requires information that are only available down the compilation line to make sense of the data. In this case, I believe it requires type information to make the distinction and properly parse it.

a < b > ( 1 )

This is ambiguous. It can be interpreted as (a) < (b) > (1) or as generics application a<b>(1) where b is a type.

So languages like Rust add a distinction factor. Rust's generics application syntax starts with ::. So instead of Buffer::new<Int>(), you write Buffer::new::<Int>(). They call it turbofish, because it looks like a fish. ::<>

I could have gone with the same turbofish, but I don't find it aesthetically pleasing, so I went for more succinct Buffer::new:[Int]()

So that is where the : operator is used, and it will be ambiguous to use it for range as well.

PS: I guess we could make an exception here. Any: followed by a [ could be parsed as generics application, otherwise it is parsed as range.

https://www.quora.com/Why-is-C++-grammar-so-hard-to-parse

Comment on lines +319 to +336
## STRINGS
- Strings are a sequence of valid UTF-8 codepoints and they are delimited by double quotes `"`.

```swift
let language = "Ratio"
```

- String interpolation is done with a pair of curly braces
```js
var story = "{language} was started in the {year}"
```

- String operations

```js
var concatenate = "ab" + "c" // "abc"
var multiply = "ab" * 2 // "abcabc"
```

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should model the string more like a high-level type and not a primitive type, the operator behaviour should be determined by the operator overloading methods of the string type

Copy link

@appcypher appcypher Nov 14, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

String is not a primitive type. There are no notion of primitiveness in my proposal.

There are

  • scalar or primitive types, which are machine-level types because your CPU can operate on them efficiently. These are types that are 1 to 8-byte wide. Basically, the builtin integer and floating-point types.
  • high-level or complex types, which build on scalar types

+ and * are overloaded for the String type. String is a high-level type because it builds on scalar types. Implementation-wise, a string type would look like this.

type String {
    length: Int, 
    capacity: Int,
    buffer: *U8, // pointer to UTF-8 buffer
}

Comment on lines +793 to +802
## UNSAFE BLOCK
- The unsafe block is necessary for scoping the usage of unsafe/unstable features like pointer arithmetic and destructive casting.

```rust
let base_ptr = some_ref as *Int

let base_ptr = unsafe {
base_ptr.add(20)
}
```

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No no no, the pointer is a torture feature. We handle the memory within the language, we might add the pointer and memory access feature but personally, pointers scare me.

Copy link

@appcypher appcypher Nov 14, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Go, Rust, Swift, Java all have pointers. Rust and Go expose them in a safe way. That is no pointer arithmetic, which may cause pointer aliasing problems. For unsafe stuff like pointer arithmetic, a lot of languages just lets you shoot yourself in the foot, but Rust does the right thing here with unsafe blocks. It limits the bug surface area, but you can still shoot yourself in the foot.

Unfortunately, when you need to go low-level, or interface C libraries, you need to have pointers in the language! If this is going to be a statically-typed language, pointers need to provided be otherwise you won't be able to do a lot of low-level shenanigans.

Comment on lines +890 to +913
## GENERICS
- Declaring a generic type

```rust
type Buffer[T] {
buffer: *T,
capacity,
len
}
```

- Declaring a generic function

```swift
impl Buffer[T] where T: Integer {
func new[T]() -> Self {
Self {
buffer: allocator:[T](10),
capacity: 10,
len: 0,
}
}
}
```

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The generic type will be used to create super types like tuple, sting e.t.c

Copy link

@appcypher appcypher Nov 14, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I already replied on those

Comment on lines +838 to +885
## IMPORT
- Ratio is a module-based language. To use things from other modules, you have to import from them.

```py
import .dir {foo}
```

- Ratio has a special file called `index.ratio`. It is used to expose modules in a particular folder. Modules of a folder can only be accessed if the folder has an `index.ratio` that exposes them.

```py
import .dir
```

If there is a folder called `dir`, this import can only be valid if there is a file path `./dir/index.ratio`

If there is no folder called `dir`, then there has to be a file `./dir.ratio`


- Ratio has two kinds of imports.

#### Regular relative import

The imported module is relative to the source file defining the import. The import symbol is preceded by a dot.

```py
import .dir
```

`dir` here can either be `./dir.ratio` or `./dir/index.ratio`

#### Root relative import

The imported module is relative to the project's root folder or it is some external dependency.

```go
import dir
```

`dir` here can either be `src/dir.ratio` or `src/dir/index.ratio` or some external dependency named `dir` with a path `src/index.ratio`.

- Ratio supports aliasing an imported element '

```py
import ext {foo}
import .module.some {foo as bar}

foo()
bar()

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the import, we should only use the last approach and eliminate the use of . before a module path. Any module imported should be looked up in the relative folder, then other paths where the language module can be such as the project root folder.

Copy link

@appcypher appcypher Nov 14, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is going to cause unintended behavior. I'm afraid, Python got it a bit right here.

If the import resolver goes from relative to root in its search, it is going to result in unwanted behavior where you actually want it to choose a module from root rather than relatively.

This will inadvertently lead to having to rename modules, just to get import resolver to work right. I'd rather be explicit here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants