Code Monkey home page Code Monkey logo

Comments (24)

Ashvin-Ranjan avatar Ashvin-Ranjan commented on June 5, 2024

The main problem with this is that we would need to make a way of keeping this backward compatible, maybe instead we can make const and switching the standard to that?

from n-lang.

SheepTester avatar SheepTester commented on June 5, 2024

The main problem with this is that we would need to make a way of keeping this backward compatible, maybe instead we can make const and switching the standard to that?

We can make var raise a warning rather than an error for immutable variables

from n-lang.

SheepTester avatar SheepTester commented on June 5, 2024

We could eliminate the first issue by rewriting makeCounter. Maybe if a function takes a var int, it takes a reference to a mutable variable of type int. For example,

let makeCounter = [_: ()] -> (var int -> int, int) {
  let state = 0
  return [state: var int] -> int {
    var state = state + 1
    return state
  }, state
}

let counter, var state = makeCounter(())
print(counter(state)) // prints 1, state = 1
print(counter(state)) // prints 2, state = 2
print(counter(state)) // prints 3, state = 3

For this example it's obviously overcomplicated, but you can imagine how a library can put all its state in an aliased record type so that the outer user

from n-lang.

SheepTester avatar SheepTester commented on June 5, 2024

There are a lot of functions for lists like append prepend remove that could use mutable arguments, but it would be rather inconvenient to chain them if they don't return any useful value. Ideally, both of the following should do the same thing:

let var temp: list[str] = []
append("wow", temp)
append("weee", temp)
prepend("wow2", temp)
remove(1, temp)
let list = temp // ["wow2", "weee"]
let list = []
  |> append("wow")
  |> append("weee")
  |> prepend("wow2")
  |> remove(1)

The latter case demonstrates that append etc. should return a list, and the former case demonstrates that the functions should take a var list[str]. Perhaps, then, append's type should be [t] t -> var list[t] -> list[t]

This suggests that for the latter case, passing in a list[t] to a function that takes var list[t] will make a copy (or at least, it won't mutate the original list). This is inefficient, though; why should the list be cloned at every step?

Ideally, because the list remains the same from one step to the next, internally, the N executor should use the same list in memory for each step. Maybe this can just be an optimisation, though. After all, if append returns the mutable list, it won't be mutated any more since the function has ended.

from n-lang.

SheepTester avatar SheepTester commented on June 5, 2024

I also wonder if it is possible to do something like (int, var str) -> bool or str -> var bool. What would they mean? How should the grammar or type checker prohibit weird uses of var? If var is like a type operator, then it'd be possible to do (at least in the syntax)

let wow: var var var var var var var var var str = "weee"

I think (int, var str) -> bool makes sense, but it implies that there's a mutable reference inside an immutable tuple, which seems not so safe. Not sure. I think the entire tuple should also be mutable, but then

str -> var bool could make sense if the function declares a mutable variable of type var then returns a mutable reference to it. However, does it make a difference whether it's var bool or bool? Since the function has ended, it can't mutate its bool variable anymore, so it might as well be immutable. Thus, it'd just be an optimisation to return the mutable value straight up regardless of whether it returns bool or var bool (like Rust, transferring ownership out of a function).

Also, maybe it's possible that you want to use a function that takes a mutable reference on a clone of a mutable value. I guess one way would be to use an identity function since it doesn't have var so it has to clone. This also means that print's type signature would have to change to [t] var t -> t if it were a true identity function. Hmm, idk.

Though I guess come to think of it, if a function returns a mutable reference from its arguments, then that mutable reference can still be changed afterwards. 😳

from n-lang.

SheepTester avatar SheepTester commented on June 5, 2024

Hmm, but it'd still be nice to tell whether someFunction(value) is pure or not without having to look up whether value and someFunction both are var

from n-lang.

SheepTester avatar SheepTester commented on June 5, 2024

Hmm, but it'd still be nice to tell whether someFunction(value) is pure or not without having to look up whether value and someFunction both are var

Maybe we could use pointers instead or something, idk, like someFunction(&value)

from n-lang.

Ashvin-Ranjan avatar Ashvin-Ranjan commented on June 5, 2024

Pointers are scary, very very scary

from n-lang.

SheepTester avatar SheepTester commented on June 5, 2024

Well, Python has them too; lists and other data types are usually passed around by reference rather than cloning everything

from n-lang.

Ashvin-Ranjan avatar Ashvin-Ranjan commented on June 5, 2024

True

from n-lang.

Ashvin-Ranjan avatar Ashvin-Ranjan commented on June 5, 2024

I think that this is the way to go. For functions, we can do int -> var str -> bool, so you can mutate the value of the string in the function.

from n-lang.

SheepTester avatar SheepTester commented on June 5, 2024

Actually, maybe instead of let var, we can require a type annotation on mutable variables and then move var to the type annotation, like

let wow: var str = "sheep"
var wow = "wiggle"

Requiring a type annotation is so we know what goes inside an empty list (let list: var list[str] = [])

from n-lang.

Ashvin-Ranjan avatar Ashvin-Ranjan commented on June 5, 2024

So instead of variable modifiers var will be a type modifier?

from n-lang.

SheepTester avatar SheepTester commented on June 5, 2024

Kind of, hmm.

Maybe I'll also make it required to use the var keyword whenever passing around the mutable reference.

So mutable values represent the same values in memory, so

let a: var list[int] = [1]
let b: var list[int] = var a // Simply `a` would clone the list
append(2, var b) // Simply `b` would clone the list, so the function would return a new list without modifying b
print(a) // Clones `a`, which is fine; giving `var a` would be a type error. Prints [1, 2]

Actually, requiring type annotations is still quite cumbersome. Maybe we should still have let var as a shorthand.

Also, a value of type t is assignable to var t, making a clone of the value, but var t is not assignable to t.

Would something like var [1] make sense? Or should var as an operator be limited to variable names?

Also, it might be surprising behaviour if a user forgets to add var in append(2, var b). Maybe cloning a var value could be explicit, like only either append(2, var b) or append(2, clone b) and disallow append(2, b) if the function takes a var value. Explicitly saying var helps indicate that the function has side effects.

To implement a function appendTwice, which appends a value twice, it could be defined as

let appendTwice = [[t] item:t list:var list[t]] -> var list[t] {
  append(item, var list)
  append(item, var list)
  return var list // Returns the mutable reference to the given list, for chaining with pipes (to avoid unnecessary cloning)
}

So even if a variable like a or list in the above examples are mutable, you still explicitly have to do var a/var list; a on its own is not acceptable. Hmm

What about function type variables? print : [t] t -> t implies that print should be able to take a mutable value, and it returns another mutable value, though not necessarily the same mutable reference in memory. I suppose it works

What about currying/functions returning functions? str -> int should always return the same int given the same str, but if it came from giving a function var list[str] -> str -> int, then that might not be the case:

let badAppend = [list:var list[str] item:str] -> int {
  append(item, var list)
  return len(list)
}

let var myList = ["hello"]
print(badAppend(var myList, "hi")) // 2

let adder: str -> int = badAppend(var myList)
print(adder("wow")) // 3
print(adder("wow")) // 4??

This is terrible! I think the solution here could be that functions cannot take a mutable type if they return a function

from n-lang.

SheepTester avatar SheepTester commented on June 5, 2024

Okay, so I guess here's a summary of the proposal so far:

  • We're introducing a new type of types: var mutable types
    • In type annotations, it's an operator you can apply to any type
      • TODO: Is it fine for a mutable type to be inside an immutable type, like (str, var list[int])?
      • In let statements, you can take advantage of type inference by using let var to infer a type and then make it mutable. For example, let var wow = [1, 2, 3] is the same as let wow: var list[int] = [1, 2, 3]
        • NOTE: Presumably this means you cannot specify a type annotation if you use let var?
        • TODO: Is it an error if a type such as [], none, or 1 cannot be immediately and unambiguously inferred alone, or can we figure it out from later usage?
    • Mutable values can be passed around like their immutable counterpoints.
      • To pass a value of type var t to what should be a var t (e.g. as a function argument or in a let statement), you must use the var keyword to indicate you're passing along a mutable reference and it could be changed.
        • TODO: This requirement might be unnecessarily verbose. I might rethink this.
        • If you want to clone the value and create a mutable reference to the new value, there'll probably be a clone function for that
      • To pass a value of type var t to what should be a t (passing a mutable value as an immutable value), it must be cloned explicitly. Probably this can just be a global function clone. It'll be cloned implicitly. [I committed a brain fart and meant to make cloning explicit for var t -> var t, not this]
    • Immutable values can be turned into a mutable value.
      • To pass a value of type t to what should be a var t, nothing needs to be done. It'll be implicitly cloned, though in most cases cloning probably isn't necessary.
        • TODO: Should value cloning be more strictly defined or should it be left as something implementations can optimise?
    • Functions that take a mutable type cannot return a function. This also means that the mutable type can only be used as the last argument of a function expression.
    • We continue to uphold the idea that a function accepting and returning immutable types should remain pure.

from n-lang.

Ashvin-Ranjan avatar Ashvin-Ranjan commented on June 5, 2024

I think we should allow casting between mutable and immutable types.

from n-lang.

ky28059 avatar ky28059 commented on June 5, 2024

My opinions:

  • I think it's easier to have two keywords for mutability (like kotlin's var/val or js's let/const) instead of let var which is cumbersome
  • Would var list[int] mean you can modify the list itself? In kotlin var x: List<t> just means you can reassign it to another list; if you wanted to do x[0] = blah you'd need a MutableList<t>
  • On passing a value of t where var t is needed, how would that work / when would that be used? I'm assuming something like
function foo(var t: int) { }

seeks to mutate t, which is why it explicitly requires var (otherwise t: int would work fine). If this is the case, you cannot pass t into something that requires var t, as that would violate mutability.

let var a = 3
let b = 3
function foo(var t: int) { t += 3 }
foo(a) // fine
foo(b) // not good

Otherwise, if passing a t into a var t doesn't mutate t, what's the point of asking for a var t then?

from n-lang.

Ashvin-Ranjan avatar Ashvin-Ranjan commented on June 5, 2024
  • I think it's easier to have two keywords for mutability (like kotlin's var/val or js's let/const) instead of let var which is cumbersome

I do feel like it is better to make a const keyword instead of adding a modifier now that you say it

  • Would var list[int] mean you can modify the list itself? In kotlin var x: List<t> just means you can reassign it to another list; if you wanted to do x[0] = blah you'd need a MutableList<t>

Lists are going to be immutable, the way that we currently assume someone would set an item in a list is using the upcoming .. operator, as you cannot set an item in a list.

For your last point I am still unsure of how we should go about it

from n-lang.

ky28059 avatar ky28059 commented on June 5, 2024

For your last point I am still unsure of how we should go about it

The way kotlin does it is simply not allow you to mutate values (at least to my knowledge). All function parameters in kotlin are immutable vals and they are passed by value instead of reference.

fun foo(x: Int) {
    x += 3 // illegal: val cannot be reassigned
}

The way c# does it is that all variables are mutable, but are still passed by reference unless you use one of the proper keywords.

int x = 5;
void foo(out int x) {
    x = 3;
}
foo(out x);
Console.WriteLine(x); // 3

The behavior of the c# out parameter keyword is what I assumed var t was trying to accomplish. I think adding var t is fine, as long as you don't allow passing in immutable ts to something that requires var t.

from n-lang.

Ashvin-Ranjan avatar Ashvin-Ranjan commented on June 5, 2024

Yeah, maybe we can use var instead of out in this situation.

from n-lang.

SheepTester avatar SheepTester commented on June 5, 2024

I do feel like it is better to make a const keyword instead of adding a modifier now that you say it

Most languages except for JavaScript use let for constants, and let is already used for constants in existing versions of N. I think we could find a new keyword for mutable variables. I believe Rust does let mut and Swift uses var

  • Would var list[int] mean you can modify the list itself? In kotlin var x: List<t> just means you can reassign it to another list; if you wanted to do x[0] = blah you'd need a MutableList<t>

Yes. Currently in N if you do let x: list[t] = ..., x can't be reassigned nor its contents modified. I don't think there's an observable difference (other than performance) between reassigning and changing the contents of a list if the list binding itself is mutable.

Since these are mutable references, you can do something like

let changeToWow = [string: var str] -> () {
  var string = "wow"
}

let var happy = "happy"
changeToWow(var happy)
print(happy) // "wow"

In the future, mutable strings can also be used like a StringBuilder in Java for performance reasons

  • On passing a value of t where var t is needed, how would that work / when would that be used?

Some functions like append might both take and return a mutable reference to a list (for chaining function calls). That way, you can pass in an immutable list and use its return type:

let someFunction = [numbers: list[int]] -> ... {
  let newList = append(0, numbers)
  ...
}

If this is the case, you cannot pass t into something that requires var t, as that would violate mutability.

If the function requires var t, N can implicitly clone t so that the function can mutate the cloned t while preserving the original t

Hmm, I suppose this might cause some silent errors if someone accidentally passes an immutable value to a function expecting it to change. Currently var is required to pass a mutable reference to a function, so that would raise an error if done on an immutable variable, but if the programmer also forgets to do that, then it becomes a silent error

Lists are going to be immutable, the way that we currently assume someone would set an item in a list is using the upcoming .. operator, as you cannot set an item in a list.

Immutable by default; with this proposal, we'll be able to allow lists to be mutated such as setting an item if the list is mutable

from n-lang.

ky28059 avatar ky28059 commented on June 5, 2024

If the function requires var t, N can implicitly clone t so that the function can mutate the cloned t while preserving the original t

Hmm, I still think it's still better to explicitly clone the immutable variable before using it as a mutable variable.

val x = 3
fun whatever(x: Int): Int {
    var sum = x
    while (sum < 100) sum *= 2
    return sum
}
println(whatever(x))

Some functions like append might both take and return a mutable reference to a list (for chaining function calls). That way, you can pass in an immutable list and use its return type

Not sure what this means. Could append not take and return a value instead?

fun append(elem: Int, arr: List<Int>): List<Int> {
    return listOf(*arr, elem)
}

from n-lang.

SheepTester avatar SheepTester commented on June 5, 2024

append doesn't need a mutable reference to the list indeed, but for performance reasons it's probably preferable to just mutate the original list instead of var list = append(item, list), which would create an entire new list

Explicit cloning is fine sure

from n-lang.

Ashvin-Ranjan avatar Ashvin-Ranjan commented on June 5, 2024

@SheepTester Should this be closed with the creation of #218

from n-lang.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.