November 21, 2019
Am Thu, 21 Nov 2019 03:10:28 -0800 schrieb Walter Bright:

> On 11/21/2019 12:18 AM, Johannes Pfau wrote:
>> TypeScript does that. In addition, it disallows dereferencing if the pointer is in the undefined state. So it forces you to check a pointer before you dereference it. I always found that to be quite useful.
> 
> I would find that to be annoying, as the CPU hardware checks it before dereferencing it, too, for free.

As Ola mentioned it's most useful as part of a complete flow-typing system and much of the benefit in TypeScript is also caused by having nullable and non-nullable types:

void foo (Nullable!T value)
{
    if (value)
        bar(value);
    else
    	bar2();
}

In the call to bar, value is known to be not null, so the type is no longer Nullable!T but T instead. So essentially, this provides a natural way to safely go from nullable to non-null types. Heavy use of non-null types makes sure you don't have to insert if () checks just to satisfy the compiler. In TypeScript the amount of false positives for this error message actually seemed to be quite low.

-- 
Johannes
November 21, 2019
On Wednesday, 20 November 2019 at 04:59:37 UTC, Walter Bright wrote:
> https://github.com/dlang/dmd/pull/10586
>
> It's entirely opt-in by adding the `@live` attribute on a function, and the implementation is pretty much contained in one module, so it doesn't disrupt the rest of the compiler.

Here's a thing:

  struct LinearFile {
      import std.stdio: File;
      protected File file;

      // [1]
      static void open(string name, void delegate(LinearFile*) @live f) {
          auto lf = LinearFile(File(name));
          f(&lf);
      }

      void close() { // [3]
          file.close();
      }
  }

  string readln(scope LinearFile* f) {
      return f.file.readln;
  }

  void live_close(LinearFile* f) {
      f.close;
  }

  void main() {
      import std.stdio: write;

      LinearFile.open("/etc/passwd", delegate(f) @live {
          write(f.readln);
          //f.close; // not an error to omit this [2]
      });

      void firstline(LinearFile* f) @live {
          write(f.readln);
          // f.close; // still dangling after this
          f.live_close;
      }
      LinearFile.open("/etc/passwd", &firstline);

      void better(LinearFile* f) @live { // [4]
          write(f.readln);
          destroy(f); // error to omit this
          // write(f.readln); // error to use f after destroy
      }
      LinearFile.open("/etc/passwd", &better);
  }

The idea of course is to have a resource that's statically
guaranteed to be cleaned up, by giving the caller a @live
requirement to not leave a pointer dangling, and no
non-borrowing function to pass the pointer to, except for the
clean-up function.

With references:

1. I don't know how (or if it's possible to) require callers to be
@live though, so instead I require that a passed delegate be @live.

2. @live doesn't seem to work with anonymous delegates though, as
the f.close isn't required here.

3. methods also seem to be non-borrowing, so the LinearFile.close
method is a bug. Which isn't convenient for imports at least:

  import linear: LinearThing, close;

4. ...and it was only when I started to add that it was a shame
that you can't just use destructors, that I thought to try them.
November 21, 2019
On 11/21/2019 4:28 AM, Jacob Carlborg wrote:
> On Thursday, 21 November 2019 at 11:10:28 UTC, Walter Bright wrote:
> 
>> I would find that to be annoying, as the CPU hardware checks it before dereferencing it, too, for free.
> 
> Why would you wait until runtime when the compiler can do it at compile time?

The antecedent said "it forces you to check a pointer
before you dereference it"
November 22, 2019
On Friday, 22 November 2019 at 04:55:57 UTC, Walter Bright wrote:
> On 11/21/2019 4:28 AM, Jacob Carlborg wrote:
>> Why would you wait until runtime when the compiler can do it at compile time?
>
> The antecedent said "it forces you to check a pointer
> before you dereference it"

You usually have an escape like "object!.memberfunction()" that tells the compiler that the programmer is certain that he does the right thing.

November 22, 2019
On Friday, 22 November 2019 at 04:55:57 UTC, Walter Bright wrote:
> On 11/21/2019 4:28 AM, Jacob Carlborg wrote:
>> On Thursday, 21 November 2019 at 11:10:28 UTC, Walter Bright wrote:
>> 
>>> I would find that to be annoying, as the CPU hardware checks it before dereferencing it, too, for free.
>> 
>> Why would you wait until runtime when the compiler can do it at compile time?
>
> The antecedent said "it forces you to check a pointer
> before you dereference it"

I'd sell it not as "it forces you to test" but "it reliably
promotes nullable types to non-nullable types when this is safe"

Kotlin example:

  fun Int.test() = println("definitely not null")
  fun Int?.test() = println("could be null")

  fun main() {
      val n: Int? = 10
      n.test()
      if (n != null)
          n.test()
  }

Output:

  could be null
  definitely not null

So if you test a nullable type for null, everything else in the
control path works with a non-nullable type. The effect on the
program is that instead of having defensive tests throughout your
program, you can have islands of code that definitely don't have to
test, and only have the tests exactly where you need them. If you
go too far in not having tests, the compiler will catch this as an
error.

I've never used TypeScript, but Kotlin's handling of nullable types
is nice to the point I'd rather use the billion dollar mistake than
an optional type.

IMO what's really exciting about this though is that the compiler
smarts that you need for it are *just short* of what you'd need for
refinement types, so instead of just

  T?  <-- it could be T or null

you can have

  T(5) <-- this type is statically known to be associated with the
           number 5.

And functions can be defined so that it's a compile-time error to
pass a value to them without, somewhere in the statically known
control path, doing something that associates them with 5.

Similarly to how you can't just hand a char[4] to a D function with
a signature that's asking for a char[5] - but for any type, for any
purpose, and with automatic promotion of a char[???] to char[5] in
the same manner that Kotlin promotes Int? to Int

Add a SAT solver, and "this array is associated with a number that
is definitely less than whatever this size_t is associated with",
and you get compile-time guarantees that you don't need bounds
checking for definite control paths in your program. That's cool.
ATS does that: it's a compile-time error in ATS to look at argv[2]
without checking argc.
November 22, 2019
On Friday, 22 November 2019 at 04:55:57 UTC, Walter Bright wrote:
> On 11/21/2019 4:28 AM, Jacob Carlborg wrote:
>> On Thursday, 21 November 2019 at 11:10:28 UTC, Walter Bright wrote:
>> 
>>> I would find that to be annoying, as the CPU hardware checks it before dereferencing it, too, for free.
>> 
>> Why would you wait until runtime when the compiler can do it at compile time?
>
> The antecedent said "it forces you to check a pointer
> before you dereference it"

I think that is looking at it from the wrong angle.

The power of optional types is being able to declare a non-optional type.

I have done a lot of Kotlin programming the last 2 years. The beauty is that when I see a T, I know it cannot be null. I know the compilers knows that, and I know my colleagues know that as well.

If you don't want the check, just use T. If you cannot use T (because you might need it to be null), use T?, and then you need the check. The point is, in the case you end up using T?, you needed the check regardless.

As an example, just like to need to check whether a range isn't empty before you front it, the first() function in Kotlin (defined on collections) returns a T?, which needs to be checked before usage. But again, you needed that anyways.
November 22, 2019
On Friday, 22 November 2019 at 06:43:01 UTC, mipri wrote:
> I've never used TypeScript, but Kotlin's handling of nullable types
> is nice to the point I'd rather use the billion dollar mistake than
> an optional type.

Kotlin has type inference, but TypeScripts has to be able to describe existing JavaScript data-models, so that it can provide stricter typing from existing JavaScript libraries. As a consequence TypeScript has to allow "crazy" mixing of types. Thus a function can have  parameters and a return type that is a union of "incompatible" types. To make this work it fully embraces flow-typing, that is: the type is determined by all possible execution paths within a function. Thus TypeScript has many features to deal with "undefined" (unitialized or non-existing variable) and "null" (missing object) as you have to deal with those from JavaScript code. And of course, this becomes a very valuable tool when transitioning code to TypeScript from JavaScript, or from a prototype to production code.

The verification language Whiley is following the same core flow-typing principle, but since the type system of Whiley employs a SMT engine it has more potential than TypeScript IMO. See page 8 in http://whiley.org/download/GettingStartedWithWhiley.pdf . (I don't think it is production ready, but interesting nevertheless).

November 22, 2019
On Friday, 22 November 2019 at 07:40:30 UTC, Ola Fosheim Grøstad wrote:
> to deal with those from JavaScript code. And of course, this becomes a very valuable tool when transitioning code to TypeScript from JavaScript, or from a prototype to production code.

Nnngh, I clearly meant transitioning code from JavaScript to TypeScript.

Kinda like how D can be used for transitioning less safe C code to more safe D code.


November 22, 2019
Am Thu, 21 Nov 2019 20:55:57 -0800 schrieb Walter Bright:

> On 11/21/2019 4:28 AM, Jacob Carlborg wrote:
>> On Thursday, 21 November 2019 at 11:10:28 UTC, Walter Bright wrote:
>> 
>>> I would find that to be annoying, as the CPU hardware checks it before dereferencing it, too, for free.
>> 
>> Why would you wait until runtime when the compiler can do it at compile time?
> 
> The antecedent said "it forces you to check a pointer before you dereference it"

But only if you don't know for sure already that the pointer can't possibly be null. With non-null types (by default) such situations are rare.

You should really have a look at TypeScript / Kotlin some time. In Javascript, aborting scripts due to null-pointers in untested codepaths has be a main reason of bugs and TypeScript really nicely solved this. I think non-null types + these automated conversion from nullable to non- null are the most important innovations for OOP code in recent years and the feature i miss most when doing OOP in D.

-- 
Johannes
November 22, 2019
On 2019-11-20 08:19:46 +0000, Walter Bright said:

> On 11/19/2019 11:49 PM, Manu wrote:
>> I haven't read thoroughly yet, although I have been following along
>> the way and understand the goals... but I really can't not say
>> straight up that I think `@live` is very upsetting to me.
>> I hate to bike-shed, but at face value `@live` is a very unintuitive
>> name. It offers me no intuition what to expect, and I have at no point
>> along this process has any idea what it means or why you chose that
>> word, and I think that's an immediate ref flag.

+1

> The "live" refers to the data flow analysis which discovers which pointers are "live" or "dead" at any point in the flow graph. This is critical for what O/B is trying to do.

So, the name is related to how it's done, not what it gives to the user. That's like selling a thing and stating: "Look this was milled, turned etc." but not telling the customer what it does.

> `@ownerBorrow` just seems a little awkward :-)

Well, why not make it catchy: @borrow

I'm sure 99.99% of all people would get a hint what this is about.

-- 
Robert M. Münch
http://www.saphirion.com
smarter | better | faster