Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

A type for meaningless values (similar to unit type) #95

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

RealyUniqueName
Copy link
Member

@RealyUniqueName RealyUniqueName commented Nov 14, 2021

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 RealyUniqueName changed the title A type for meaningless values (similar to uit type) A type for meaningless values (similar to unit type) Nov 14, 2021
@Aurel300
Copy link
Member

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?

@RealyUniqueName
Copy link
Member Author

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.

@back2dos
Copy link
Member

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

@Simn
Copy link
Member

Simn commented Nov 15, 2021

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.

@TheDrawingCoder-Gamer
Copy link

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

@hughsando
Copy link
Member

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.

@haxiomic
Copy link
Member

haxiomic commented Nov 15, 2021

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?

@TheDrawingCoder-Gamer
Copy link

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.

@Simn
Copy link
Member

Simn commented Nov 15, 2021

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.

@frabbit
Copy link
Member

frabbit commented Nov 15, 2021

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

@frabbit
Copy link
Member

frabbit commented Nov 15, 2021

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

@frabbit
Copy link
Member

frabbit commented Nov 15, 2021

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

@hughsando
Copy link
Member

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.

@RealyUniqueName
Copy link
Member Author

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

@hughsando
Copy link
Member

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"

@frabbit
Copy link
Member

frabbit commented Nov 16, 2021

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/

@Aurel300
Copy link
Member

@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.)

@frabbit
Copy link
Member

frabbit commented Nov 16, 2021

@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
Copy link
Member

frabbit commented Nov 16, 2021

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

@frabbit
Copy link
Member

frabbit commented Nov 16, 2021

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
  }
}

@TheDrawingCoder-Gamer
Copy link

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?

@Aurel300
Copy link
Member

Aurel300 commented Nov 16, 2021

@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.

@frabbit
Copy link
Member

frabbit commented Nov 16, 2021

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

@frabbit
Copy link
Member

frabbit commented Nov 16, 2021

@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

@TheDrawingCoder-Gamer
Copy link

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

@RealyUniqueName
Copy link
Member Author

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

@TheDrawingCoder-Gamer
Copy link

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

@ALANVF
Copy link

ALANVF commented Nov 17, 2021

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>

@skial skial mentioned this pull request Nov 17, 2021
1 task
@0b1kn00b
Copy link

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

@montibbalt
Copy link

"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)

@kaikoga
Copy link

kaikoga commented Jan 14, 2022

If this is a poll issue I'm familiar with the Unit name because of Rx.NET or UniRx.

Reusing Any with value null in this purpose may work without adding something to current std but it looks like you can theoretically extract the underlying value, so meh.

@fal-works
Copy link

fal-works commented Mar 26, 2022

There are certainly some cases where unification is desired rather than converting everything here and there to NoUse yourself.

However I also agree with this:

please let's not keep the boxed values around

As it is not ideal that the from Dynamic approach causes boxing everywhere.

If it is not possible to replace all values to a single one when casting to NoUse while also solving the variance issue, perhaps we should also consider the "classic" Unit type without any unification and see which one is better (compared to the proposed NoUse), if not best, even if the strict Unit might have limited benefits as a large part of the existing code is based on Void.
In either case it is still nice to have the automatic generation of return expressions as proposed.

That said, what I personally wish is a super loose unification like the below (without runtime cost if possible), but I know this should be quite controversial.

// Functions with Void/Any
var fnVoidBool:() -> Bool = () -> true;
var fnBoolVoid:Bool->Void = _ -> {};
var fnBoolAny:Bool->Any = v -> v;
var fnVoidVoid:() -> Void = () -> {};

// Void/Any to Noise
var fnNoiseBool:Noise->Bool = fnVoidBool;
var fnBoolNoise:Bool->Noise = fnBoolVoid;
var fnBoolNoise2:Bool->Noise = fnBoolAny;
var fnNoiseNoise:Noise->Noise = fnVoidVoid;

// Noise to Void
var fnVoidBool2:() -> Bool = fnNoiseBool;
var fnVoidVoid2:() -> Void = fnNoiseNoise;

@fal-works
Copy link

fal-works commented Mar 26, 2022

And regarding the name, I personally think:

  • NoUse, Unused, Unknown or Noise
    • Should be fine even if the underlying value can be anything.
    • I like Noise, but might this be confusing when used separated from Signal?
  • Unit, Nothing, Empty or NoData
    • To me these sound good if the actual value is always a native null or any other single value.
    • However the actual implementation might differ; I feel that (null: Null<Unit>) may suggest something other than (unit: Unit) while the other names may not.
    • I'd prefer Unit if it (with the actual behavior) does not confuse those who already know Unit from other languages.
  • In either case I'd prefer the one-word ones but maybe it's not essential.

@Uzume Uzume mentioned this pull request Apr 24, 2022
@EliteMasterEric
Copy link

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

Not sure if this discussion is ever going to get resolved, but this abstract did what I wanted it to.

@EliteMasterEric
Copy link

With Haxe 5.0, Void is being disallowed as a return type, and NoValue is being added to the standard library to replace it!

HaxeFoundation/haxe#11558

@Geokureli
Copy link

With Haxe 5.0, Void is being disallowed as a return type, and NoValue is being added to the standard library to replace it!

HaxeFoundation/haxe#11558

It seems the opposite, where Void will ONLY be allowed as a return type, and NoValue will replace it in all other cases

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.