Comments (24)
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.
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.
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.
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.
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.
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.
Hmm, but it'd still be nice to tell whether
someFunction(value)
is pure or not without having to look up whethervalue
andsomeFunction
both are var
Maybe we could use pointers instead or something, idk, like someFunction(&value)
from n-lang.
Pointers are scary, very very scary
from n-lang.
Well, Python has them too; lists and other data types are usually passed around by reference rather than cloning everything
from n-lang.
True
from n-lang.
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.
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.
So instead of variable modifiers var
will be a type modifier?
from n-lang.
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.
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 usinglet var
to infer a type and then make it mutable. For example,let var wow = [1, 2, 3]
is the same aslet 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
, or1
cannot be immediately and unambiguously inferred alone, or can we figure it out from later usage?
- NOTE: Presumably this means you cannot specify a type annotation if you use
- TODO: Is it fine for a mutable type to be inside an immutable type, like
- Mutable values can be passed around like their immutable counterpoints.
- To pass a value of type
var t
to what should be avar t
(e.g. as a function argument or in alet
statement), you must use thevar
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 at
(passing a mutable value as an immutable value),it must be cloned explicitly. Probably this can just be a global functionIt'll be cloned implicitly. [I committed a brain fart and meant to make cloning explicit forclone
.var t
->var t
, not this]
- To pass a value of type
- Immutable values can be turned into a mutable value.
- To pass a value of type
t
to what should be avar 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?
- To pass a value of type
- 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.
- In type annotations, it's an operator you can apply to any type
from n-lang.
I think we should allow casting between mutable and immutable types.
from n-lang.
My opinions:
- I think it's easier to have two keywords for mutability (like kotlin's
var
/val
or js'slet
/const
) instead oflet var
which is cumbersome - Would
var list[int]
mean you can modify the list itself? In kotlinvar x: List<t>
just means you can reassign it to another list; if you wanted to dox[0] = blah
you'd need aMutableList<t>
- On passing a value of
t
wherevar 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.
- I think it's easier to have two keywords for mutability (like kotlin's
var
/val
or js'slet
/const
) instead oflet 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 kotlinvar x: List<t>
just means you can reassign it to another list; if you wanted to dox[0] = blah
you'd need aMutableList<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.
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 val
s 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 t
s to something that requires var t
.
from n-lang.
Yeah, maybe we can use var
instead of out in this situation.
from n-lang.
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 kotlinvar x: List<t>
just means you can reassign it to another list; if you wanted to dox[0] = blah
you'd need aMutableList<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
wherevar 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 requiresvar 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.
If the function requires
var t
, N can implicitly clonet
so that the function can mutate the clonedt
while preserving the originalt
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.
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.
@SheepTester Should this be closed with the creation of #218
from n-lang.
Related Issues (20)
- Exponent not returning a float value at runtime
- `\r` is not getting removed by `strip`
- Functions with generics appear to have an extra argument at runtime HOT 2
- Better way of setting items in lists
- Printing out records still isn't accurate
- `request.request` sending data does not work
- Allow `getItem` and `entries` to be class methods
- Can't deconstruct value of `parseInt` and `parseFloat`
- Dividing two ints returns a float.
- Class types sometimes get exploded into records during type checking instead of just displaying the name.
- Cannot assign a `[]` to a `list` type HOT 1
- Runtime error occurs when using `|>` on an incorrect type
- Program does not exit quietly sometimes HOT 1
- FileIO byte functions don't work. HOT 1
- There is no `XOR` operator
- Update `pyinstaller` dependency for next update.
- `FileIO` operations are relative to the file that was run first, not the actual file.
- There is no way to deal with `multipart/form-data` HOT 1
- N does not have any direction HOT 1
- FileIO does not work HOT 1
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
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.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from n-lang.