ziglang / zig

General-purpose programming language and toolchain for maintaining robust, optimal, and reusable software.
https://ziglang.org
MIT License
34.32k stars 2.51k forks source link

[PROPOSAL] change XOR bitwise operator symbol #8196

Closed igotfr closed 3 years ago

igotfr commented 3 years ago

proposal: change XOR bitwise operator symbol (currently ^) to #

motivation: future use of ^ to exponentiation

languages and others that use ^ as exponentiation: Haskell, Julia, Matlab, R, Lua, Google, calculators

why #: # is not used for nothing in Zig, unlike C and Rust who uses it for other stuffs

ikskuh commented 3 years ago

Not sure if that's a good idea.

Languages that use ^ for xor are: C, C++, C#, Java, bash, perl, javascript, ... which are more similar to Zig than the languages you listed as an example (except lua)

Also there's a reason that pow is a function and not a operator/builtin: It's horrible slow.

But if we were to introduce a power operator, i'd recommend using ^^ as a symbol

Guigui220D commented 3 years ago

@MasterQ32 or ** but I'm against an operator for pow too

D1CED commented 3 years ago

@Guigui220D Zig already has the ** operator for array repetition.

jedisct1 commented 3 years ago

Exponentiation is mostly useful in two contexts:

A dedicated operator only makes sense in languages where bignums are part of the language itself.

Everywhere else, ^ is usually the XOR operation; using a different operator in Zig would only introduce confusion in addition to being a breaking change without much benefits.

RogierBrussee commented 3 years ago

As explained at length in #7512 and vividly illustrated by #7605, using the shift operator for multiplication by 2^n is a bad idea. It is much clearer to say a 2^3 + 5 then (a << 3) + 5. Note that, oops, a <<3 + 5 means a 2^(3 + 5). Just as important, a * 2^n has the undefined behaviour in the way one should expect from multiplication by a power of 2 unlike a<< n . (for a:u64, (a << n) expression has undefined behaviour if n >= 64 but will happily shift off bits from a, which makes perfectly good sense if we think of << as a bit operator acting on a bit vector, but is undefined behaviour when considered as multiplication by 2^n)

This alone should warrant the introduction of a power operation. It is true that the power operation can be expensive, but that never stopped an efficient language like Fortran having an exponent (albeit written **). One can restrict exponents to unsigned integers (or even more draconian, comptime_int non negative integers).

Shifts work on the level of the bit representation and should only work on a bitvector datatype.

Also, xor is actually a quite rare operation, even in low level code like the Linux kernel. Therefore if people don't like # one can just use @xor(), an infix "xor" keyword or if one feels really brave: the two character /\ combination.

igotfr commented 3 years ago

invalid justifications:

C does not use the predecessors symbols for bitwise operators:

AND was ^ not & OR v not | XOR ⊻ or ⊕ not ^ NEGATION ¬ not !

the mentioned languages are historically full of problems and the manner that features are implemented in it (I include Dart here) always cause disapproval both by developers and medium and reddit bloggers, while Julia, Matlab, Lua, Elixir are semantically well accepted

one of the Zig slogans is "C but with the problems fixed". So I think I made myself understood, so please don't use this justification


valid and real justification:

the reason to an exponentiation operator in Zig is easiness and ergonomics. The best example are the iterators functions and methods like map, filter, reduce, etc. Everyone knows they are slower than for loops, inclusive by benchmarks, however, it is used a lot for the reasons I already mentioned, inclusive in Rust

If the operator for exponentiation is implemented, the developer must be warned, preferably with benchamarks between the pow function

D1CED commented 3 years ago

Maybe what we really need is a literal syntax for powers of two and not a dedicated exponentiation operator.

And surprise: We already have such a syntax in the language with hexadecimal floating point literals with binary exponentiation 0x1pA reading as 1_16 * 2^(A_16) so 2^10 (n_b meaning n as interpreted to the base b). This syntax may look foreign to some but I think it is pretty readable.

Taking your example:

a * 0x1p3 + 5

One could go and allow the use of p in decimal floating point constants, dropping the 0x prefix.

An annoyance is currently that the compiler is not true to the spec and requiring an explicit cast to an integral type for floating points with exponentiation even though the spec says:

Float literals coerce to any floating point type, and to any integer type when there is no fractional component.

Which is clearly not the case.

matu3ba commented 3 years ago

valid and real justification:

* [heavy_check_mark] "Also there's a reason that pow is a function and not a operator/builtin: It's horrible slow"

the reason to an exponentiation operator in Zig is easiness and ergonomics. The best example are the iterators functions and methods like map, filter, reduce, etc. Everyone knows they are slower than for loops, inclusive by benchmarks, however, it is used a lot for the reasons I already mentioned, inclusive in Rust

zig is a system programming language and no Julia (intended for convenient math stuff), so easiness and ergonomics are less important than making performance bottlenecks explicit.

matu3ba commented 3 years ago

Maybe what we really need is a literal syntax for powers of two and not a dedicated exponentiation operator.

And surprise: We already have such a syntax in the language with hexadecimal floating point literals with binary exponentiation 0x1pA reading as 1_16 * 2^(A_16) so 2^10 (n_b meaning n as interpreted to the base b). This syntax may look foreign to some but I think it is pretty readable.

Taking your example:

a * 0x1p3 + 5

One could go and allow the use of p in decimal floating point constants, dropping the 0x prefix.

An annoyance is currently that the compiler is not true to the spec and requiring an explicit cast to an integral type for floating points with exponentiation even though the spec says:

Float literals coerce to any floating point type, and to any integer type when there is no fractional component.

Which is clearly not the case.

This would also hide the potential performance bottleneck in the number representation.

igotfr commented 3 years ago

@matu3ba I agree that performance is the priority in Zig and if ergonomic features cause performance bottlenecks to the binary even if not used, then it should not be implemented, otherwise, I do not see problem

The proposal is a optional feature and not a substitute for the pow function. Use if you want, knowing the consequences

Not only "high level" languages have syntax sugar, Rust is the best example and it is used to the same goal (system programming language), using functional programming and optional immutability

I know that performance, stability and readability is the focus in Zig, to remind and put that in question in this kind of proposal is tedious, please argue only about the problems and conflicts that this would bring to the language, this proposal is not priority, but I think it should be considered

matu3ba commented 3 years ago

Not only "high level" languages have syntax sugar, Rust is the best example and it is used to the same goal (system programming language),

Rust has very slow compile times, because they stuff alot codegen and static analysis (including a big type system) into the compiler. For example Fuchsia decided for that reason not to use it in the Kernel.

using functional programming

If you mean iterators, closures and alike: They increase compile time heavily (and make running debug builds (without optimisation) slow). It remains to be seen, how and if they can separate these on file-level/crate-level for faster compile times (to be viable for the Linux Kernel).

and optional immutability

You have to decide for variables between const and var for that very reason. This sounds like you did not use zig yet. Maybe you can have a look in Julia (optionally compiled to quite optimal code) or Lua (only luajit extreme simple language) for your math use cases?

igotfr commented 3 years ago

@matu3ba the Rust compiler detects compile time errors (https://doc.rust-lang.org/stable/error-index.html), potential problems generated by bad practices (warnings) and suggestions on how to fix it, all of this, in addition to a big type system and macros, it ends up making the compiler really heavy

of these, the only ones that I would find interesting to bring to Zig would be:

Iterators don't depend on the compiler, so it's just a matter of bringing it to std or not, which I really don't mind (https://doc.rust-lang.org/src/core/iter/traits/iterator.rs.html#623-629, https://github.com/ziglang/zig/pull/940), but the rest of what I quoted depends on changes to the compiler

What I don't understand is:

const is not the same as immutability (https://docs.rs/im/15.0.0/im/) and it is used to save memory

I've used Julia, and although very performative, it has a lot of structural problems compared to Elixir or ReasonML. I'm comparing Zig with Rust, not Julia

matu3ba commented 3 years ago

@cindRoberta zig favors fast compile times and having sufficient debug information to fix the problem. Rust favors static analysis to detect the problem (which brings slow compile times).

functional programming, anonymous functions, pipe operator, closures

FP is known to make compile times (very) slow and one should use Rust for for in-build FP (+ static analyis). If one prefers codegen, I would advise to do verification + static analysis and lower those things to zig.

Iterators + range types are there, but there is not a pile of codegen on top of it. (think of map and filter etc)

what problems a little delay to the compile time would bring?

delays due to static analysis and codegen pile up and are design choices never to be changed back (too much churn)

generate compile time delay

  1. stuff that is not used, is not tested
  2. cache behavior gets worse if your code base becomes more complex
  3. simpler compiler passes are faster to optimise and easier to maintain
  4. Why do you believe do all formal verification tools have simple languages as target? (like cogent)

a parallel compiler

Please reflect on why Rust did not do that yet.

used to save memory

This looks like static analysis + convenient syntax support to do COW. I dont understand, why this cant be done in std.

igotfr commented 3 years ago

@matu3ba

Please reflect on why Rust did not do that yet.

Rust has a lot more basic structural problems than Zig, it would be easier to make another language. The only structural Zig problems I know of are:

matu3ba commented 3 years ago

@cindRoberta One does not reach critical mass by copying the goals of Rust (C++ replacement) with the methods of Rust, since Rust is "sufficiently good enough" in what they are doing (and they have a very persuasive escape hatch with editions to break stuff).

Better have the base language extremely fast and put something like cogent as static analysis+formal tool on top. Tradeoff is you need a spec of valid syntax for codegen (which Rust does not have) and keep the AST<->source code mappings in sync (for lookup via tooling etc).

Its just that people want stuff now and not wait until zig is finished (enough) for the syntax to be stable as codegen and static analysis target.

igotfr commented 3 years ago

@matu3ba this issue proposes to change the operator ^ is because I know that this will hardly be done after version 1.0, I don't want to rush things, our bilateral conversation is just for me to try to understand some things, for example: in Zig I can get closures with a function with a inner struct a which has a method, so I call the struct method inside the function and it works normally, but I can't have a function inside another directly:

fn outer(comptime x: u32) (fn(u32) u32) {
  const InnerSt = struct {
    fn inner(y: u32) u32 {
      return y * x + 3;
    }
  };

  return InnerSt.inner;
}

I will open issues for the switch and the struct too, but I already know that there will have many dislikes and will probably be closed

matu3ba commented 3 years ago

@cindRoberta It probably easier to discuss these kind of things on discord or any community forum: https://github.com/ziglang/zig/wiki/Community

You can find me on discord or use another channel.

farteryhr commented 1 year ago

just curious why lua is mentioned multiple times here but nobody seem to mention the solution devised by lua 5.3 which i think is elegant: just reuse ~ as a binop...

some further thinking of mine about ^ is: also let / be a unop "reciprocal, inverse", ^/ can be fused as a "n-th (real) root" binop, detecting x^/2 x^/3 for special case. i admit this is quite of math flavor, or scripting flavor for who's too lazy to type sqrt or find the hypot in newer standards.....