HackerTrans
TopNewTrendsCommentsPastAskShowJobs

mraleph

no profile record

comments

mraleph
·9 เดือนที่ผ่านมา·discuss
We are done with NNBD transition for a few years now.

Dart 2.12 (released March 2021) introduced null-safety.

That started a 2 year transitionary period during which you could mix nullsafe (language versions 2.12 or above) and non-nullsafe (language versions below 2.12) code in one program.

Dart 3.0 (released May 2023) removed support for language versions prior to 2.12 - meaning that you can no longer opt out of null-safety.
mraleph
·2 ปีที่แล้ว·discuss
At some point in my life (when I briefly worked on LuaJIT for DeepMind) I have written a stack walker which can stitch together native and Lua frames: for each native stack frame it checks if that is actually an interpreter frame or a trace frame - if that's the case it finds corresponding `lua_State` and unwinds corresponding Lua stack, then continues with native stack again.

This way you get a stack trace which contains all Lua and native frames. You can use it when profiling and you can use it to print hybrid stack traces when your binary crashes.

I was considering open-sourcing it, but it requires a bunch of patches in LJ internals so I gave up on that idea.

(There is also some amount of over-engineering involved, e.g. to compute unwinding information for interpreter code I run an abstract interpretation on its implementation and annotate interpreter code range with information on whether it is safe or unsafe to try unwinding at a specific pc inside the interpreter. I could have just done this by hand - but did not want to maintain it between LJ versions)
mraleph
·2 ปีที่แล้ว·discuss
> - it is truly native, in the sense I can directly talk to native APIs on each platform without going through bridging code and there's no special runtime

I thought Compose Desktop is running on JVM an renders through Swing/AWT? Did this change?
mraleph
·2 ปีที่แล้ว·discuss
> The fact that I can’t then immediately go

Who said you can't? :) This actually works in Dart:

    Dog? dog;
    bool isDog = dog is Dog;
    
    if (isDog) {
      dog.bark();
    }
i.e. boolean variables which serve as witnesses for type judgements are integrated into promotion machinery.

I do agree with you that

1. In general there is no limit to developers' expectations with respect to promotion, but there is a limit to what compiler can reasonably achieve. At some point rules become too complex for developers to understand and rely upon. 2. Creating local variables when you need to refine the type is conceptually simpler and more explicit, both for language designers, language developers and language users.

I don't hate excessive typing, especially where it helps to keep things simple and readable - so I would not be against a language that does not do any control flow based variable type promotion. Alas many developers don't share this view of the world and are vehemently opposed to typing anything they believe compiler can already infer.

It's all a matter of personal taste in the end.
mraleph
·2 ปีที่แล้ว·discuss
The reason why languages promote variable types based on control flow is because developers en masse actually expect that to happen, e.g. facing the code like

    Dog? maybeDog;
    if (maybeDog != null) {
      maybeDog.bark();
    }
If compiler says "sorry, maybeDog might be null", developer (rightfully so) usually responds "but I have just checked and I know it is not, why do you bother me?". So languages chose to accommodate this.

> What if I want to set the value back to nil if it is not-nil?

You can. The type of the variable does not actually change. You could say that the information about more precise type is simply propagated to the uses which are guarded by the control flow. The following will compile just fine:

    Dog? maybeDog;
    if (maybeDog != null) {
      maybeDog.bark();
      maybeDog = null;
    }
> Why should I have to wrap things back in an optional if I want to pass it along as such?

You don't, with a few exceptions. There is a subtype relationship between T and T?: T <: T?, so the following is just fine:

    void foo(Dog? maybeDog);

    Dog? maybeDog;
    if (maybeDog != null) {
      maybeDog.bark();
      foo(maybeDog);  // Dog can be used where Dog? is expected
    }
 
You might need to account for it in places where type inference infers type which is too precise, e.g.

    Dog? maybeDog;
    if (maybeDog != null) {
      // This will be List<Dog> rather than List<Dog?>. 
      final listOfDogs = [maybeDog];
    }
Though I don't think it is that bad of a problem in practice.
mraleph
·2 ปีที่แล้ว·discuss
In Dart 3 you have patterns and sealed class families to achieve that. See my comment above[1].

The syntax is unfortunately more verbose, but if all goes well we will address that issue this year.

[1]: https://news.ycombinator.com/item?id=39613415
mraleph
·2 ปีที่แล้ว·discuss
In Dart 3 instead of declaring the variable and then comparing you can simply write an if-case pattern:

    if (value case final value?) {
      // value is a fresh final variable 
    }
mraleph
·2 ปีที่แล้ว·discuss
In Dart you use class hierarchies instead, rather than enums (which in Dart are way to define a compile time constant set of values). So the original example becomes:

    sealed class MySomething<T> {
    }

    final class One extends MySomething<Never> {
    }

    final class Two extends MySomething<Never> {
      final Child child; 
      
      Two(this.child);
    }

    final class Three<T> extends MySomething<T> {
      final T value;

      Three(this.value);
    }

    final class Four extends MySomething<Never> {
      final int Function() callback;

      Four(this.callback);
    }
And then you can exhaustively switch over values of MySomething:

    int foo(MySomething<String> foo) {
      return switch (foo) {
        One() => 1,
        Two(child: Child(:final value)) => value,
        Three(:final value) => int.parse(value),
        Four(:final callback) => callback(),
      };
    }
The declaration of a sealed family is considerably more verbose than Swift - and I really would like[1] us to optimized things for the case when people often declare such families. Macros and a primary constructors will likely provide reprieve from this verbosity.

But importantly it composes well with Dart's OOPy nature to achieve things which are not possible in Swift: you can mix and match pattern matching and classical virtual dispatch as you see fit. This, for example, means you can attach different virtual behavior to every member of the sealed class family.

[1]: https://github.com/dart-lang/language/issues/3021
mraleph
·3 ปีที่แล้ว·discuss
> Dart+Flutter look promising until you notice that they run Skia WASM inside the dart VM which itself runs in WASM which runs inside a JS VM and performance is abysmal.

That's not how Flutter works on either of the platforms it supports.

When you run natively (e.g. mobile, desktop, etc) Skia or Impeller execute natively and all your Dart code is compiled ahead-of-time to native code as well. No JS or Wasm is involved anywhere in the stack. No JIT compilation in release binaries, only during development.

When you run on the Web - then naturally Skia is compiled to Wasm and Dart code is compiled to JS or Wasm, JS VM will end up running both of those. No Dart VM is involved anywhere here.
mraleph
·3 ปีที่แล้ว·discuss
[disclaimer: I work on Dart, though not on the language team]

With primary constructors this will become something along the lines of:

    sealed class Message();

    class IncrementBy(final int value) extends Message;

    class DecrementBy(final int value) extends Message;

    class Set(final int value) extends Message;
Which is considerably less repetitive, though `extends Message` is still there. I am fairly optimistic that the next step would be to eliminate that[1], though I think we would need to gather a bit more feedback from users as people are getting more and more reps in with Dart 3.0 features. I personally would prefer something along the lines of:

    sealed class Message() {
      case IncrementBy(final int value);
      case DecrementBy(final int value);
      case Set(final int value);
    }
Current syntax is not all that bad if you are going to do OO and add various helper methods on `Message` and its subclasses, but if you just want to define your data and no behavior / helpers - then it is exceedingly verbose.

[1]: https://github.com/dart-lang/language/issues/3021
mraleph
·3 ปีที่แล้ว·discuss
> it was built on top of v8

Was never built on top of V8 in any shape of form. Dart VM does not even share any code with V8.

Dart 1 was designed by people who originally started V8 (Lars Bak and Kasper Lund), that's the only connection.
mraleph
·4 ปีที่แล้ว·discuss
Dart does not have arrays, it has lists. Try replacing "Dart array" with "Dart list" instead.
mraleph
·4 ปีที่แล้ว·discuss
It would be interesting to see 1-1 comparison with LuaJIT interpreter _without corresponding runtime changes_. That would given a meaningful baseline for how much you potentially loose/gain using this approach.

Current measurement presents comparison of two wildly different implementation strategies: LuaJIT Remake uses IC and hidden classes while LuaJIT proper does not. It's a well known fact that ICs+hidden classes can lead to major performance improvements. That insight originated in Smalltalk world and was brought to dynamic language mainstream by V8† - but unfortunately it muddles the comparison here.

† V8 was open sourced on September 2, 2008... Coincidentally (though probably not :)) JSC landed their first IC prototype on September 1, 2008.
mraleph
·4 ปีที่แล้ว·discuss
> The JIT still performs better, but the AOT code starts up faster.

I should update that side note because a lot of things has changed. It should probably say something like:

> Theoretically, Dart VM JIT should have best peak performance, while Dart VM AOT has best startup time. In reality the comparison is quite complicated. AOT has seen a lot of work in the last few years, which made it faster than JIT in some aspects e.g. both direct and highly polymorphic method calls are usually faster in AOT. JIT's peak performance has been languishing in a state of neglect, but it still runs circles around AOT in some cases due to aggressive inlining.

Something like this.
mraleph
·4 ปีที่แล้ว·discuss
The extension has recently reached Stage 2 - most of the questions around type system have been resolved. V8 has a working implementation. We have build `dart2wasm` compiler targeting this and it shows good numbers.
mraleph
·4 ปีที่แล้ว·discuss
> what's interesting is that static typing buys you somewhat minimal gains in performance.

There are a lot of caveats here. You could potentially claim "minimal gains in peak performance if you can apply adaptive JIT compilation techniques", but even that is stretching it somewhat.

Adaptive JITing comes at a price in terms of warmup, memory usage and implementation complexity. Fixed object shapes help somewhat to reduce the amount of checks needed but they don't take you all the way there. Optimising numerics remains challenging (e.g. think about optimising the case where a field always contains a `double` floating-point value or a field that always contains a 64-bit integer value). Knowing the shape of the container does not yield any information about the shape of elements which implies that some checks have to stay behind in the loops. Yes, monomorphic checks are usually simple (compare+branch) but polymorphic are not. And so on and so forth.

Yes, Dart 1 is easier to compile into efficient code compared to JavaScript. Dart 2 is even easier though - because it is more statically typed.

> but fear that it painted itself into a corner by first targeting memory managed languages.

FWIW WASM GC is coming - and it looks great.
mraleph
·15 ปีที่แล้ว·discuss
Right title for this 2 years old article is "Why V8 is not suitable for nginx".

He has his own set of requirements which by no means applies to _all_ webservers.