Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

Hi,

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++ }



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.


not all abstractions are leaky, many compilers/language grammars enforce the paradigm of local lexical contexts.

Playing a game without accept its self imposed restrictions whose player accepted voluntarily would be lying to oneself.


Thanks for your input. I'll do more research on this.


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.


I don't think it's possible to have a closed source language in 2019 :) It will be released with an open source this year.


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.


https://github.com/thepowersgang/mrustc is an alternative compiler for Rust.


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.


JavaScript isn't quite as new as those listed, but it has at least 5 or 6 implementations.


Yeah, JS too.


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".


Cool! Looks like that’s not quite ready for prime time, but I hope it succeeds.


While older than 10 years, it still kind of falls under newish, but D has three compilers.


Python has PyPy?


Python isn’t new! It’s almost 30 years old.


I was about to suggest Python 3, but it seems Python 3.0 released December 3, 2008. Barely missed it!


There's no reference implementation yet.

I had a small page about the language up. I don't have some of the most basic things implemented and figured out yet.


I’m very happy to see that you’re doing this. Keep at it!


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.


I'm planning to optimize this so that no copies are created.


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.


For what it’s worth, if you can optimize away the immutability (copying), I’d much prefer expressing myself this way, rather than using mutability.


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


> (and this will later be optimized by the compiler of course)

Do not underestimate this part. It can take a surprising amount of effort to get these things right and performant.



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.


Ah yes. I understand a little more the context behind your post. And I don't disagree with the points you raised there either.

Thank you for replying :)


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() { ... }
and calling the method with an instance

    x := Something{}
    x.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


pass by copy is a traditional safe mechanism in functional languages, esp. for multithreading. It can also be made fast enough.


Keep in mind that many of these functional PLs encourage datatypes like first class linked lists that make this copy semantic very fast.


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.


    > 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?


I think yes, it's like declaring every single argument as const.


Algol68 had this property. Consider

  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.


If you can't mutate an argument, does that mean you can't call any of the argument's methods that mutate it, either?


Right.


ever studied Limbo? look at Limbo instead of Go.


Isn't the main reason why languages like Go and Rust don't do that because it would produce a ton of memory garbage?


It will be optimized by the compiler.


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?


What I mean is `a = sort(a)` will be optimized to `sort(&a)` which can mutate internally.


What about:

  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.


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.


How? In-place sorts and out-of-place sorts have different implementations.


Ada does not allow functions that alter parameters for their callers (out mode parameters).


Yes. Don’t mutate in place :thumbsup:


Pure functions in Fortran do this.


Also D with the immutable type qualifier.

https://dlang.org/spec/function.html#pure-functions




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: