> It seems easy to accidentally make allocating code allocate by changing some variable names (b = fnc(a) - oops, allocation)
> I would be extremely wary of trusting a compiler to realize what sort of allocations are optimizable and not - I already have problems with inlining decisions, poor code layout, etc in fairly optimized c++/rust code compiled by gcc+llvm.
Replace allocation with any other expensive work that duplicating a datastructure would result in, and you have the same story. I suspect many of the people that would be evaluating this compared to C/C++/Rust have similar performance concerns.
Playing a game without accept its self imposed restrictions whose player accepted voluntarily would be lying to oneself.
I think you're right that it can often be an antipattern. But there are also use cases (usually for performance reasons), and the real problem occurs when you are not expecting the modification to happen. If the parameter is annotated then it's obvious that it might be mutated, and less of an issue...
P.S. Looking forward to the open source release. This langiage looks pretty nice/interesting to me, but there's no way I would invest time into learning a closed source lanaguage.
I can’t think of any well-known newish language (created in the last 10 years, say) with multiple implementations. Rust, Kotlin, Swift, Julia, Dart... any others?
Go might be a counterexample with its cgo implementation, but that was built by the same team and I have the impression (maybe mistaken) that it’s fallen by the wayside.
I don’t know if this indicates a really new language development style, with less emphasis on specification, or if it’s just a cyclic thing and some of these new languages will gain more implementations as they get more established.
Perhaps only Golang (go and go-gcc).
For all others, everybody uses the standard compiler. Even in Python, PyPy is not even 10% of the users.
Whereas in C/C++ and other such older languages there are several implementation (MS, GCC, LLVM, Borland, Intel) with strong uptake and strong communities/companies behind them.
It's way harder to develop tooling for a language which is only defined as "what its compiler can compile".
I had a small page about the language up. I don't have some of the most basic things implemented and figured out yet.
Overall if you don’t know Ada I’d recommend that you take a look at the features of it. It has very similar design goals as your V.
There's a false dichotomy between functions and methods, which are simply (sometimes dynamically dispatched) functions with a special first argument. If you allow mutable first arguments, why not any argument?
Instead of the language deciding what's mutable and not, I'd rather have a const system like C/C++ to ensure that changes aren't happening behind the programmer's back.
Re: "not allowing to modify function arguments except for receivers" -- maybe instead all fields const by default, but having something like an optional mut modifier?
A quick question, how does hot reloading work (with presumably AOT compilation and no runtime)?
(Perhaps there's a OS mechanism to change already loaded code in memory that I should know).
Both Rust and Swift require specifically opting into parameter mutation (respectively `&mut`[0] and `inout`) and the caller needs to be aware (by specifically passing in a mutable reference or a reference, respectively), only the receiver is "implicitly" mutable, and even then it needs to be mutably bound (`let mut` and `var` respectively).
Inner mutability notwithstanding, neither will allow mutating a parameter without the caller being specifically aware of that possibility.
The ability to mutate parameter is useful if not outright essential, especially lower down the stack where you really want to know what actually happens. Haskell can get away with complete userland immutability implicitly optimised to mutations (where that's not observable), Rust not so much.
[0] or `mut` in pass-by-value but the caller doesn't care about that: either it doesn't have access to the value anymore, or it has its own copy
Do not underestimate this part. It can take a surprising amount of effort to get these things right and performant.
a) how do you plan to do this?
b) will the optimization still kick in if you name the return value something else than “a”?
c) what if “a” is an argument passed to the function that calls “multiplyBy2”? Then doing an in-place update would modify the value of “a” for some other function that has also been passed “a” as an argument.
I do wish the author the best with this project though. Plus designing your own language is fun :)
Thank you for replying :)
Why not go the other way and pass everything as a reference? After all that's what Java does, and that's how you'd pass any struct in C anyway. It's a rare case when I need to forbid the calling function to not modify an argument for whatever reason or because I don't trust it - in that case you can make a copy beforehand or use a const modifier. But in most cases I'd expect functions to modify the memory that I pass in, instead of allocating and returning new structures.
Why not have a simple `class` construct as in JavaScript? Keeping functions together in a class is very convenient and means you don't have to pass the struct as the first argument each time. That way `Array` can be a class, and would always be passed by reference. No ambiguity there, class instances are always mutable. Everyone is already familiar with it, it works.
A class method can simply map to a global C function: ``` ClassName_MethodName(*self, ...) ```
As an aside, using a syntax that people are already familiar with (and APIs!) would be great, and make something like this instantly usable. JavaScript has a fairly small core API which would be easy to emulate for example.
This already seems to have a way to associate functions with data structures, in the same way that methods are done in Go, via a "receiver" before the function name
Eg.
type Something{}
fn (self mut Something) method() { ... }
and calling the method with an instance x := Something{}
x.method()This looks like a really interesting project, and I look forward to trying it out!
object = object._replace(foo=1)
But I usually encapsulate the _replace call in the class so it's more like: object = object.update_foo(1)
I personally find it can make the code easier to understand, like you say, but it seems like you're getting a lot of disagreement from the other comments.The disagreement they're getting is not on the use of immutability and pure transformations, it's on the fantasy that a "sufficiently smart compiler" would be able to optimise this (especially non-trivial versions of this) into mutations under the cover.
Furthermore, V is apparently supposed to be a fairly low-level language, if you have to rely on the compiler to perform the optimisation, can't statically assert that it does so[0] and it fails to, that is extremely costly in both "machine" time and "programmer" time (as you'll start wrangling with the compiler's heuristics to finally get what you need).
If you want immutability, do it, but do it properly: build the runtime and datastructures[1] and escape hatches which make it cheap and efficient, don't handwave that the compiler will solve the issue for you.
[0] and if you are you may be better off just adding mutation to the language already
[1] non-trivial immutable data structures are tree-based and translate to a fair number of allocations, you really want an advanced GC
That's a solved problem, the "random blobs" is called a tuple. Or, since the language is inspired by Go, you can have bespoke multiple return values instead of a reified generic structure.
So I would say that having to explicitly mark function parameters as mutable (like in Rust) is a better approach.
> This is an idea I got that isn't really implemented
> in any language I know of.
Is this different than an arg declared as a const reference in C++. Or since fields are still mutable, like `final` arguments in Java? STRUCT (INT x, y) z = (1, 2)
Here z is a const struct. If you wanted a variable, you'd have to say STRUCT (INT x, y) z;
z := (1, 2)
or equivalently REF STRUCT (INT x, y) z = LOC STRUCT (INT x, y) := (1, 2)
Basically a variable has a REF mode. x := &a
a = sort(a)
print(x) // Sorted?
To detect whether something can be mutated in place will require static analysis to see if there are aliases or pointers to the data. If this is an optimization that's based on whether something is safe to mutate in-place, you'll run into the problem where performance becomes different depending on whether something can be optimized or not. For example, adding "x" makes the sort call suddenly perform worse since the compiler sees that it can't mutate "a" in-place.This is assuming that you allow multiple aliases to the same data. The reason Rust has lifetimes and borrowing is precisely to be safe about mutation. Rust wouldn't allow sort() to modify "a" in-place in the above code.
And you probably want to pick one of sorting in-place and out-of-place.
I say "rule of thumb" and I mean it that way. Sometimes there will be Haskell-specific answers. But if your programming language has less code metadata available than Haskell but is promising more optimizations, it's worth a cross-check. I agree with you strongly in this particular case; without type system support for this specific case, it's going to be very hard to optimize away things like a sort. You start getting into the world of supercompilation and whole-program optimization, the primary problems of which for both of them is that they tend to have intractable O(...) performances... but something about them makes them seem intuitively easy. I've seen a number of people fall into that trap.
(I haven't personally, but I understand the appeal. My intuition says they shouldn't be that difficult too! But the evidence clearly says it is. Very, very clearly. We're talking about things like optimizations that are super-exponential in complexity, but seem intuitively easy.)
This probably means lots of aliasing restrictions or in the case where the optimization can't be done, copying could be an expensive side effect of an innocent refactoring.
I hear Swift uses something like this, though it's copy-on-write. I've not used Swift in any significant capacity. Does anyone else have experiences to share with this model?
A good way to imagine this is having return value optimizations give you a copy which is placed on the original which allows the work to be skipped. But that can require a whole lot of other trades around calling conventions, optimization boundaries and so on. C++ has dealt with some of this complexity recently but it’s nuances too years to sort out between standard revisions and only became required in some cases rather than legal until after compilers had plenty of time to work on it.
Developer here. I was going to post this here in a couple of weeks after launching the product and creating a separate site for the language with much better information about it.
I'd also like to hear your opinion about not allowing to modify function arguments except for receivers. This is an idea I got that isn't really implemented in any language I know of.
For example: mut a := [1, 2, 3]
So instead of multiply_by_2(&a)
we have to return a new array (and this will later be optimized by the compiler of course)
a = multiply_by_2(a)
I think this will make programming in this language much safer, and the code will be easier to understand, since you can always be sure that values you pass can never be modified.
For some reason all new languages like Go, Rust, Nim, Swift use a lot of mutable args in their stdlibs.
You can still have methods that modify fields, this is not a pure functional language, because it has to be compatible with C/C++:
fn (f mut Foo) inc() { f.bar++ }