haxe-evolution icon indicating copy to clipboard operation
haxe-evolution copied to clipboard

A type for meaningless values (similar to unit type)

Open RealyUniqueName opened this issue 3 years ago • 36 comments

Unlike Void, which implies no value at all, NoUse type would allow to express a value with no meaning in places where a value has to exist.

class Signal<T> {
	function trigger(payload:T);
}

var signal = new Signal<NoUse>();
signal.trigger(null);

Rendered version

RealyUniqueName avatar Nov 14 '21 14:11 RealyUniqueName

But why call it NoUse? Why not Unit (since as far as I can tell this type is not similar to unit type, it is a unit type)? Why not NoData as was already established in other Haxe libraries?

Aurel300 avatar Nov 14 '21 19:11 Aurel300

We can call it Unit, but iiuc this name comes from the idea that the type has a single value. If that doesn't bother anyone I would prefer Unit too.

Why not NoData as was already established in other Haxe libraries?

That name does not exactly reflect what's going on because in runtime there actually may be any data.

RealyUniqueName avatar Nov 14 '21 19:11 RealyUniqueName

I like this. It seems unfortunate that we wind up with two types for the same thing in the stdlib though.

back2dos avatar Nov 15 '21 06:11 back2dos

We can call it Unit, but iiuc this name comes from the idea that the type has a single value.

In my mind that's true because it accepts null.

Simn avatar Nov 15 '21 07:11 Simn

I prefer either Noise or None. Noise is used by tink, while None is used by python (and I prefer it when making interpreters)

TheDrawingCoder-Gamer avatar Nov 15 '21 12:11 TheDrawingCoder-Gamer

I have used a lot of languages and when I came across 'unit' in ocaml, I was confused - so there will be some explaining to do because it is not obvious from its name. I think 'None' from python is better, although they tend to use this as a value rather than a type. I would also like to put forward the 'Empty' type - a variable with nothing in it, rather than a variable with the same (unit) thing in it that also takes 0 bytes.

hughsando avatar Nov 15 '21 13:11 hughsando

Agree that Unit is a non-obvious name, my first guess is that would be a type that has a value of 1 or something?

Empty and None are clearer but synonymous with Void

What about Unused?

haxiomic avatar Nov 15 '21 13:11 haxiomic

None is not synonymous with Void; Void is the absence of a value; It has nothing, and cannot be used as a value. None is a value with no meaning. It can be used as a value.

TheDrawingCoder-Gamer avatar Nov 15 '21 14:11 TheDrawingCoder-Gamer

Void is the absence of a value; It has nothing, and cannot be used as a value. None is a value with no meaning. It can be used as a value.

That is not intuitively clear though. C developers are confused because they're familiar with void*, Haskell developers are confused because they are familiar with Option and JS developers are confused anyway.

Simn avatar Nov 15 '21 14:11 Simn

Should this be a bottom type? Like Never in typescript or Nothing in scala or Void in haskell?

frabbit avatar Nov 15 '21 19:11 frabbit

If no, why not use a Unit type in such cases?

frabbit avatar Nov 15 '21 19:11 frabbit

Had a second look, this NoUse type is actually more similar to unknown in typescript.

frabbit avatar Nov 15 '21 20:11 frabbit

Not sure about the grammar or exact meaning of "NoUse" - I would prefer "DontUse", "NotUsed", "Useless" or "Unused" depending of what you are going for. I am coming around to Null<Void> being a type that you can only assign a single(unit) value to, namely "null". It would always be equal to null, it would logically be "zero size" (but perhaps you would store a null value in, say, an Array or Map or Dynamic situation) and you must always pass null to it as an function argument, unless it it is declared optional.

hughsando avatar Nov 16 '21 02:11 hughsando

Null<> is a completely transparent type, which may get erased in a lot of places in the compiler and macros. Also consider a function like this: function fn<T>(v:Null<T>):T

RealyUniqueName avatar Nov 16 '21 06:11 RealyUniqueName

I think erasing can be made to work - either by substituting Null<Void> with some internal Unit type early in the compilation or letting it get though as Void and outputting "null" in the backend if required. The backends that erase the Null are probably also those the don't actually need to care about the difference.

In the template example, you would need to call something like fn( (null:Null<Void>) ) When fn does something like 'return x' you would get an error "Can't return value from Void function" or "Can't create variable of type Void" or similar. I think both are reasonable responses is this case.

The compiler could also infer T: Not Void from the fact that you return it, or you have a variable of this type and so create an error in the fn calling line, "Template parameter can't be Void"

hughsando avatar Nov 16 '21 07:11 hughsando

i would really suggest taking a look at typescripts unknown (top type) and never (bottom type) types. Especially when intersection types are planned for the future. unknown and never  play important roles as identity elements. unknown & { foo: Bar} = { foo: Bar } and never | string = string . see: https://blog.logrocket.com/when-to-use-never-and-unknown-in-typescript-5e4d6c5799ad/

frabbit avatar Nov 16 '21 08:11 frabbit

@frabbit Top and bottom types are different concepts, unrelated to unit types. Top types correspond to Haxe's Any or Dynamic. Bottom types correspond to e.g. Rust's ! type and indicate that a function will never return. (Specifically, a bottom type has no inhabitants, i.e. there can never be an instance of a bottom type, so annotating a function to return a bottom type indicates it can never return because that would violate the bottom type constraint.)

Aurel300 avatar Nov 16 '21 14:11 Aurel300

@Aurel300 but the proposal says We want any type in function return type position to unify with NoUse hence from Dynamic part.. This means the value is not always null. It can be anything. This relates to typescripts unknown type which is a top type.

frabbit avatar Nov 16 '21 16:11 frabbit

typescripts unknown type is more or less the same as tinks Noise type. But that's very different from a Unit type.

frabbit avatar Nov 16 '21 16:11 frabbit

enum abstract NoUse(Null<Dynamic>) from Dynamic {
	var NoUse = null;
}

class Test {
  static function main() {
    var x:NoUse = 1; //compiles fine
    var x:NoUse = "hey"; //compiles fine
  }
}

frabbit avatar Nov 16 '21 16:11 frabbit

enum abstract NoUse(Null<Dynamic>) from Dynamic {
	var NoUse = null;
}

class Test {
  static function main() {
    var x:NoUse = 1; //compiles fine
    var x:NoUse = "hey"; //compiles fine
  }
}

isn’t that basically what tink noise is?

TheDrawingCoder-Gamer avatar Nov 16 '21 17:11 TheDrawingCoder-Gamer

@frabbit I agree with you here:

but the proposal says We want any type in function return type position to unify with NoUse hence from Dynamic part.. This means the value is not always null. It can be anything. This relates to typescripts unknown type which is a top type.

And we already have such a (top) type, it is called Any:

var x:Any = 1;
var y:Any = "hey";

So if you want to do this for signals or whatever you can already use Signal<Any>.

I disagree with this proposal in two things:

  • We don't need this "unify with anything" behaviour. What is the point? At best, it saves a line of code it takes to return a Unit. At worst, it becomes a nightmare to debug because our type system magically becomes dynamic in return positions.
  • If we actually want such unify behaviour, please let's not keep the boxed values around:
enum abstract NoUse(Int) {
  var NoUse = 0;
  @:from static function fromDynamic(_:Dynamic):NoUse return NoUse;
}

With the implementation in the proposal, even though the variable isn't accessible without casts, it remains part of the NoUse instance. Might be annoying to find out your signal chains take up more memory than they need to since the instances are never collected.

Aurel300 avatar Nov 16 '21 17:11 Aurel300

@Aurel300 I agree, my fear was that such a type was added and named Unit. Which is different ;).

frabbit avatar Nov 16 '21 17:11 frabbit

@Aurel300 The problem with your suggested implementation is that is doesn't solve the problems mentionend in the proposal  because of variance.

enum abstract NoUse(Int) {
  var NoUse = 0;
  @:from static function fromDynamic(_:Dynamic):NoUse return NoUse;
}

final f = () -> 1;

final g:() -> NoUse = f; // doesnt compile

frabbit avatar Nov 16 '21 17:11 frabbit

the None type is supposed to be a type that has no meaning, I don't see the harm in letting it unify. The only use I can think of is for generics, because sometimes you don't need a meaningful value. Maybe only let it be used in typing? Or maybe it's like Java's Void which is a "boxed void"? IDK

TheDrawingCoder-Gamer avatar Nov 16 '21 19:11 TheDrawingCoder-Gamer

Btw the name None would conflict with haxe.ds.Option which has a constructor named None.

RealyUniqueName avatar Nov 16 '21 19:11 RealyUniqueName

oh dear. runners up for me would be Nothing, NoUse.

TheDrawingCoder-Gamer avatar Nov 16 '21 19:11 TheDrawingCoder-Gamer

just an idea, maybe Null on its own could be a unit type? similar to the Null<Void> idea, with the default type params proposal, it would act like Null<T = Void>

ALANVF avatar Nov 17 '21 01:11 ALANVF

Throwing Bang in as it comes from maxmsp, and if you squint means the same thing as Noise

0b1kn00b avatar Dec 14 '21 11:12 0b1kn00b

"Unit type" is far easier to google than any of the other suggestions if the name is a concern, but it's also not quite the same thing as what's being proposed here (for example I wouldn't want the unification stuff at all if it was really meant to be a Unit type)

montibbalt avatar Jan 13 '22 18:01 montibbalt