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++:
I'm not sure if I'm the target audience for this (low-latency trading), but here's my thought - code which would allocate in a fast path is a strict no-go for me, and this runs fairly close to that in a few regards:
> 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.
You need to stop pretending they are not globals. Just accept you work on one continuous memory patch and are simply passing pointers around. If you don't lie to yourself, you can't shoot your own foot when the compiler do not see your lie.
I think Rust and Swift's approach of approach of making the user explicitly annotate (both at function definition time and a call time) these kind of parameters works pretty well.
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.
Technically (and we are technical folks) it's a language with a closed source reference implementation. A serious language designer is going to specify his language, so that other implementations, closed or open source, are possible if not available. C is the ur-example, there are dozens of implementations, some proprietary and some free.
We aren’t in a phase where languages have multiple implementations right now.
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.
Yeah, but no language of the ones the parent mentioned has a non-toy, non-personal-project alternative compiler.
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.
Yes, go has a formal specification, which not only opens the possibility for alternative implementations, but, more importantly, allows for the development of tools like linters.
It's way harder to develop tooling for a language which is only defined as "what its compiler can compile".
This. Letting the user annotate it is better then enforcing a behavior that is adequate in some scenarios but awkward in others. For gamedev for example, generating copies at game loop is only acceptable for small objects like Vectors and if they're allocated in the stack for example. Even if the compiler optimizes it, it is better to express the intent clearly in the code.
Yeah , but i meant that a = multiply_by_2(a) still looks like it is copying things even if it isn't. Let it be immutable by default and mutable with a keyword.
In Ada, procedure and function arguments must be annotated with whether they are “in”, “out”, “in out”. “In” can never be modified, “out” has no initial value and “in out” has both initial value and can be modified.
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.
Although you can certainly go the pure functions route, I wouldn't recommend it for performance.
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.
Hello, this is an excellent language! Have been looking for something like this for a long time!
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).
> For some reason all new languages like Go, Rust, Nim, Swift use a lot of mutable args in their stdlibs.
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
I wanted to ask OP about this part specifically, so I’ll write my questions here.
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.
This is exactly what I was looking for. There's no real option for a language that allows interactive coding and it's easily embeddable. Please open source asap. You will get contributors starting with me.
I’m not taking anything away from the goals of this project but I do feel it’s worth mentioning that there actually are lots of languages that offer interactive coding and are embeddable. Eg JavaScript, Python, LISP, Perl, Lua, etc. Heck, even BASIC fits that criteria.
I do wish the author the best with this project though. Plus designing your own language is fun :)
All of them have shortcomings when one wants multi-threading.
Javascript , Python and Lua implementations are single threaded. Perl is slow. LISP is ideal but the commercial distributions with these features have licesing costs of thousand $. The open source (Common Lisp) implementations have all other deficiencies: not easily embeddable and/or big image sizes and/or slow and/or with poor GC. Currently the best open source option seems to be Gambit Scheme. I am playing with it and while the author is extremely supportive some points are still a little bit rough.
In the single thread world there is already a clear winner and that is Lua.
I actually think having different models (functional and imperative) just adds to confusion. I don't think immutability is all that useful personally, unless the language is purely functional to begin with. I'd keep it simple and stick to C as much as possible.
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.
> 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.
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() { ... }
I'm thinking that it's easy to make a mistake that would prevent the optimization from happening, so I'd personally much rather be explicit about mutability than betting on having satisfied the optimizer.
This looks like a really interesting project, and I look forward to trying it out!
I have a possibly unhealthy obsession with using namedtuples in my Python and so this pattern appears frequently in my code:
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.
> 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
What about the case of multiple outputs? It's traditional to have functions that take other mutable arguments to store different auxiliary return values in. So, with this proposal, you couldn't do that and would have to construct random blobs to store all return values and then unpack them.
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.
I think it would be a bit weird if fields of structs can be modified, but bare values can't. Kind of feels like the inconsistency between `Integer` and `int` in Java.
So I would say that having to explicitly mark function parameters as mutable (like in Rust) is a better approach.
Can it really always be optimised by the compiler. For example, I imagine optimising `sort(&arr)` which cannot mutate arr could be quite difficult, no?
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.
Unless `a` is a linear value, somebody might have a reference to it, so you can't just sort it in place under the cover. The entire thing is useless if you looks like you don't have side-effects but the compiler visibly breaks this.
And you probably want to pick one of sorting in-place and out-of-place.
Yeah, as a rule of thumb I'd say "If Haskell doesn't already do this optimization, find out why."
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.)
I assume `a` would need to be copied into the local scope of the function and the optimization would be to elide the copy after analysis shows the original a is safely consumed by the call site so it does not require persistence.
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?
I don't think copy-on-write will prevent a copy here, since the copy is being written to inside of sorted. I don't think the compiler is smart enough to elide the copy, either.
It definitely can and does work for similar situations in other languages. It’s fragile though as aliasing accidentally somewhere or adding other kinds of uncertainty around which values are returned makes sinking the return allocation into the caller, a required prerequisite, much more likely to fail.
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.
Yeah, I don't doubt it if Clang can do this optimization for C++, but I don't think the Swift optimizer is quite there yet since it needs to peer through many more layers of complexity.
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++ }