haxe-evolution
haxe-evolution copied to clipboard
A type for meaningless values (similar to unit type)
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);
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?
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.
I like this. It seems unfortunate that we wind up with two types for the same thing in the stdlib though.
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.
I prefer either Noise or None. Noise is used by tink, while None is used by python (and I prefer it when making interpreters)
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.
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?
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.
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.
Should this be a bottom type? Like Never in typescript or Nothing in scala or Void in haskell?
If no, why not use a Unit type in such cases?
Had a second look, this NoUse type is actually more similar to unknown in typescript.
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.
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
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"
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 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 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.
typescripts unknown type is more or less the same as tinks Noise type. But that's very different from a Unit type.
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
}
}
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?
@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 I agree, my fear was that such a type was added and named Unit. Which is different ;).
@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
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
Btw the name None would conflict with haxe.ds.Option which has a constructor named None.
oh dear. runners up for me would be Nothing, NoUse.
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>
Throwing Bang in as it comes from maxmsp, and if you squint means the same thing as Noise
"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)