dotnet / vblang

The home for design of the Visual Basic .NET programming language and runtime library.
289 stars 65 forks source link

Comparing Integer and Integer? yields Boolean? #507

Closed VBAndCs closed 3 years ago

VBAndCs commented 4 years ago

Converting this from C#

: base(i => (!brandId.HasValue || i.CatalogBrandId == brandId) &&
                (!typeId.HasValue || i.CatalogTypeId == typeId))

to VB:

MyBase.New(
  Function(i) (Not brandId.HasValue OrElse i.CatalogBrandId = brandId) AndAlso
     (Not typeId.HasValue OrElse i.CatalogTypeId = typeId)
  )

Where brandId and typeId of type Integer? when Option Explicit and Strict are Off the VB code builds but causes runtime error which doesn't happen in C#. When I turned the options On, I got the error also in design time:

Option Strict On disallows implicit conversions from 'Boolean?' to 'Boolean'.

I solved this by using the .Value to convert Integer? to Integer:

MyBase.New(
  Function(i) (Not brandId.HasValue OrElse i.CatalogBrandId = brandId.Value) AndAlso
     (Not typeId.HasValue OrElse i.CatalogTypeId = typeId.Value)
  )

This is a flipped behavior between C# and VB.NET! Why on earth comparing an Integer with an nullable integer results a nullable Boolean? Any comparison in the universe can only yield true of false. What kind of comparison is that which can yields Nothing? A more prof that this is a bug in https://github.com/dotnet/vblang/issues/507#issuecomment-602291432 and https://github.com/dotnet/vblang/issues/507#issuecomment-602293068 And a more strange behavior (a comparison that yields an object) here: https://github.com/dotnet/vblang/issues/507#issuecomment-602362777

VBAndCs commented 4 years ago

Another Note: I tried to use a multi-line lambda:

MyBase.New(
                Function(i)
                    Return (Not brandId.HasValue OrElse i.CatalogBrandId = brandId.Value) AndAlso
                                (Not typeId.HasValue OrElse i.CatalogTypeId = typeId.Value)
                End Function)

but got this error:

Statement lambdas cannot be converted to expression trees.

Why?

zspitz commented 4 years ago

What kind of comparison is that which can yields Nothing?

Consider -- the answer to, "Is 5 equal to something unknown?" is "Unknown" -- it might or might not be equal.

Even the answer to "Is 5 equal to something which might be a number but might be unknown?", is also "Unknown".

zspitz commented 4 years ago

Statement lambdas cannot be converted to expression trees.

Are you asking why statement lambdas can't be converted to expression trees? My understanding is expression trees were first included for LINQ, which didn't need to convert statement lambdas, because expression trees themselves didn't support such constructs as blocks and statements. These were added later, and there was less of a push to enable the compiler-conversion of multiline lambdas.

pricerc commented 4 years ago

Why on earth comparing an Integer with an nullable integer results a nullable Boolean? Any comparison in the universe can only yield true of false. What kind of comparison is that which can yields Nothing?

SQL works the same way. In fact, any self-respecting database language will work the same way.

Compare anything to NULL, and the answer is NULL. Even if both values are NULL, the result is NULL, because there is no way to know if two NULLs are the same.

If the answer to a boolean comparison can be NULL, then by definition, the result is NULLABLE.

pricerc commented 4 years ago
MyBase.New(
  Function(i) (Not brandId.HasValue OrElse i.CatalogBrandId = brandId) AndAlso
     (Not typeId.HasValue OrElse i.CatalogTypeId = typeId)
  )

do you get a different result if you put explicit parentheses in?

MyBase.New(
  Function(i) ((Not brandId.HasValue) OrElse (i.CatalogBrandId = brandId)) AndAlso
     ((Not typeId.HasValue) OrElse (i.CatalogTypeId = typeId))
  )

i.e. is VB compiling your current code as?:

MyBase.New(
  Function(i) ((Not brandId.HasValue OrElse i.CatalogBrandId) = brandId) AndAlso
     ((Not typeId.HasValue OrElse i.CatalogTypeId) = typeId)
  )

I seem to recall that C# and VB have different operator priorities.

VBAndCs commented 4 years ago

do you get a different result if you put explicit parentheses in?

Was the first to try, but didn't make a difference. The error is about converting Boolean? to Boolean which comes from how VB compares a value and a nullable value.

zspitz commented 4 years ago

Compare anything to NULL, and the answer is NULL. Even if both values are NULL, the result is NULL, because there is no way to know if two NULLs are the same.

If the answer to a boolean comparison can be NULL, then by definition, the result is NULLABLE.

I remember having seen a talk by Lucien Wiscik on VB, where he mentioned that VB got it right; and the C# mode of null == null returning true was incorrect.

VBAndCs commented 4 years ago

That Is not true. Comparing any value to Nothing will yield false. Try: Dim x = 1 = Nothing X is Boolean, not Boolean? and its value = false. This is the only possible logic. but

        Dim m = 1
        Dim n As Integer? = 1
        Dim x = m = n

X is Boolean? which is totally wrong, as we have only two possible cases: Compare a number to a number = true/false Compare a number to Nothing = false So the result is always Boolean. This is definitely a bug/faulty design.

VBAndCs commented 4 years ago

More strange: Dim x = Nothing = Nothing X is a Boolean and = True! So comparing Integer? and Integer? should also yield Bool. This is what C# does, and what VB should do. As I said: Any Comparison in any planet in any universe can only yield Boolean. This is the foundation of logic.

RevensofT commented 4 years ago
        Dim m = 1
        Dim n As Integer? = 1
        Dim x = m = n

X is Boolean? which is totally wrong, as we have only two possible cases: Compare a number to a number = true/false Compare a number to Nothing = false So the result is always Boolean. This is definitely a bug/faulty design.

Nope it's not bug or faulty here, VB infer priority on what's you declare first, in this case is n As Integer?, it make VB always view n as Integer?(I usually call it maybe Integer instead nullable Integer because it make much more sense in VB) no matter what value it has.

What's should we get from Int? == Int ? Of course, not int, not bool but bool? because 0 != null in this case, we use a nullable value type because we want to check that variant has been set value or not; so when n = null; we expect x = null; too because it make us know some variant hasn't been set value, it maybe because something wrong with data read from database or a bug in code.

That why VB infer in your sample code set bool? as type of x instead just bool.

pricerc commented 4 years ago

Any Comparison in any planet in any universe can only yield Boolean. This is the foundation of logic.

Not true. It can also yield "I don't know".

Let me ask it as an algebra problem:

let x = 1 let y = n where n is a positive integer. let z = x - y

what is the value of z ?

If you don't know what n is, how can you say what z is?

If you compare any value to an Unknown value, then the only honest answer you can have is I don't know.

From a VB perspective, in this case, you're not using a 'Pure' Boolean - you're using a tri-state type with values of Yes, No and Unknown, which for convenience, we've opted to call Nullable(Of Boolean) or Boolean?. And the compiler and library writers have put rules around the semantics of that type that we have to abide by.

As for (Nothing = Nothing) = True. Nothing pre-dates Nullable(Of Boolean), so in legacy VB there was no way to return a Nullable(Of Boolean), which would be the 'most correct' response to a (Nothing = Nothing). I assume a decision was made to call that 'true', because for practical reasons, that's what most programmers are interested in, although I'd argue that it's technically wrong (and SQL has it right). For backwards compatibility, that logic needs to remain.

Also, slightly off-topic, in VB, you should not be checking if something Equals Nothing, you should be checking if something Is Nothing.

In SQL (NULL = NULL) returns NULL (NULL IS NULL) returns TRUE .

VBAndCs commented 4 years ago

More:

     Dim x = Nothing Is Nothing
        ' x is Boolean = True

        Dim s As String = Nothing
        Dim y = s = 1
        ' y is boolean = False

But this is very odd:

        Dim o As Object = Nothing
        Dim z = (o = 1)
        ' z is Object = False

A logical comparison inferred as an object is totally exotic. There is no need at all to expect any result from a comparison other than True and False (a Boolean Result). The worst case that can happen in late binding is an exception if it doesn't have an overloaded operation for equality, so it doesn't matter if z is Boolean or Object. So, the unknown state doesn't affect the result at all, and the only valid type for z is Boolean. @KathleenDollard what is the design spec for comparison operations involving objects or nullable values? What is the practical need to return an object or a nullable Boolean? It only causes errors that need extra code to avoid.

RevensofT commented 4 years ago

I think you confuse between normal compare and nullable compare, to make it simple, if any left or right of operator is nullable type, the result always be nullable type.

pricerc commented 4 years ago

@KathleenDollard what is the design spec for comparison operations involving objects or nullable values? What is the practical need to return an object or a nullable Boolean? It only causes errors that need extra code to avoid.

1) At this point, it doesn't matter. Any change to this logic would be a breaking change, which we don't do in VB-land except under extreme circumstances.

2) The system as it is makes perfect sense to me, as I suspect it does to many others, otherwise this 'problem' would have been raised some years ago already.

Padanian commented 4 years ago

Compare anything to NULL, and the answer is NULL. Even if both values are NULL, the result is NULL, because there is no way to know if two NULLs are the same.

That's a very brave decision. Any non-null is different than null, or, in other words, can I tell a difference between a null and anything else which is not it? Yes, therefore the comparison shall give FALSE or raise an exception. And whether Nothing is equal to itself, it's a matter for philosophers, but IMHO should be true or raise an exception.

Padanian commented 4 years ago

let x = 1 let y = n where n is a positive integer. let z = x - y

what is the value of z ?

We know perfectly the value of Z and it is z=1-n If n is undeclared, it doesn't compile. If it isn't initialised, than n=0 For any value of n, the calculation returns a signed integer.

Happypig375 commented 4 years ago

There are two interpretations of Nothing:

  1. A known value (Nothing = Nothing is True), aka normal equals
  2. An unknown value (CType(Nothing, Integer?) = Nothing is Nothing), aka nullable equals

Both interpretations are correct and at this point the decision is set in stone and cannot be changed because of backwards compatibility.

VBAndCs commented 4 years ago

@pricerc I think she would say that. But note that VB.NET is thought stable because it isn't tested practically in Asp.net core, where all the new features added in the last years are heavily used to provid dependency injection. This is why I stumpl whis these bugs while converting a full Asp.net coe app from c# to Vb.net. This also answers @Happypig375 and others with same opinion : This will not break anything, since no one expect a comparison will return null or an object. This is a binary programming on a binary processor. In Q# in a quantum processor you can expect three state for the logic: true/ false/ Underestimate. But this is Vb not Q#. And this is why c# does it right. They got the feedback early and took the logical design decision. It is not too late to fix that in Vb even it will not evolve. They promised to maintain stability and fix bugs. This behavior is inconsistent with other vb logical operations and raise exception. Many of you are confusing between casting from boolan? to boolean and comparing them. A comparison in binarry system in a 0/1 operation and can't be anything eslse. This is why the c# code works and should even work if we omit the gard ".HasValue" condition. I have no more to say on the matter untill we here the design prospect from the team.

YairHalberstadt commented 4 years ago

According to Anthony Green this is to spec:

https://anthonydgreen.net/2019/02/12/exhausting-list-of-differences-between-vb-net-c/#64

tverweij commented 4 years ago

@YairHalberstadt You just beat me to it

VBAndCs commented 4 years ago

So, this is not a bug, but I am still with my argument it is a faulty illogical design decision that is not a VB style nor a binary operation style. I prefer C# way in this matter. VB forces us to write longer conditions with explicit casting just to do an trivial comparison. For example we need to write:

        Dim x As Integer?
        Dim y = If(x.HasValue, x.Value = 0, False)

instead:

        Dim x As Integer?
        Dim y = x= 0

because that in last code y will be nothing. Hope this changes in any new coming VB implementation outside MS. If I went deep to Roslyn source I will make this change in my VB vision. Thanks.

Note: still don't know why comparing objects returns an object not even a nullable Boolean?

tverweij commented 4 years ago

No, this won't change. This is how VB works.

pricerc commented 4 years ago

let x = 1 let y = n where n is a positive integer. let z = x - y what is the value of z ?

We know perfectly the value of Z and it is z=1-n If n is undeclared, it doesn't compile. If it isn't initialised, than n=0 For any value of n, the calculation returns a signed integer.

There is no compiler, think of it as a question in a math test at school.

In this case, if nobody has told you what n is, the correct answer to the test question is "There is no answer, because n is unknown".

pricerc commented 4 years ago

@VBAndCs

This will not break anything, since no one expect a comparison will return null or an object

That's a VERY bold assumption.

And also, are you calling me 'no one' ? :)

pricerc commented 4 years ago

@Padanian

Firstly, I hope you are keeping safe in Italy at this stressful time.

Compare anything to NULL, and the answer is NULL. Even if both values are NULL, the result is NULL, because there is no way to know if two NULLs are the same.

That's a very brave decision.

I don't know why you think it is brave, that's the way SQL has worked for decades?

Any non-null is different than null, or, in other words, can I tell a difference between a null and anything else which is not it? Yes, therefore the comparison shall give FALSE or raise an exception.

Remember, I was talking there about SQL (which is why I've been spelling NULL in all uppercase). SQL's NULL is similar, but slightly different to VB's Nothing.

In the SQL world, a comparison that returns NULL, is often exactly raising an exception, just not one that stops execution of your query. I have extensively used NULL to highlight 'errors' in data, mostly when there are missing links between sets of data. The difference between something "Being equal to NULL" (x = NULL, which is always false) and something "Being NULL" (x IS NULL, which can be true or false) is VERY important when working with sets (remembering that SQL is a language for working with sets).

And whether Nothing is equal to itself, it's a matter for philosophers

Indeed.

whether Nothing is equal to itself ... IMHO should be true or raise an exception.

If your code has got to the point where you are trying to figure out if Nothing is equal to itself, then might I suggest that you have more than a philosophical problem?

So, IMHO: If the compiler has no way of expressing "Dude, that's a dumb question", then I think Exception is the correct response.

However, if the compiler can offer you a Nullable(Of Boolean) that allows it to express "I don't know", then the correct response is "I don't know".

KathleenDollard commented 4 years ago

This would be a breaking change.

Padanian commented 4 years ago

Well, we've got a million-line software in vb6 we can't port to vb.net because we used array of controls. Abandoning array of controls was a breaking change. @pricerc Thanks, we are trying to deal with it. We are starting to see the light at the end of the tunnel. Back to regular program: it doesn't matter whether n is known or not. This is algebra. If n is numeric, can't be nothing. Any comparison between numerics can't be nothing. Now, the only issue I see is whether a specific object is numeric or not. Inferring comes to help. And whether SQL always worked like that doesn't mean much. I know people who made the very same mistake on their job for decades. Or bugs fixed eons after code was released. In short, comparisons IMHO can't be three-headed monsters.

pricerc commented 4 years ago

I know people who made the very same mistake on their job for decades.

Yeah, I know some of them too.

CyrusNajmabadi commented 4 years ago

I prefer C# way in this matter.

Then use C#. VB is not C# and C# is not VB. Each have different rules and ways of treating their respective systems. Importantly, both highly value back compat and would only break that for extremely compelling cases.

Given that this has been VB's behavior for 15 years, and it's not a big deal, there is no way this will change.

jmarolf commented 4 years ago

in my experience I find this makes VB a better language when working will Nullable<bool>. both bool? and bool expressions can be mixed and matched without a lot of boilerplate

Dim doAction = true
Dim isSet = _service?.IsValueSet
If (isSet AndAlso doAction )
    ' Some code that responds to this option
EndIf

vs.

var doAction = true;
var isSet = _service?.IsValueSet();
if((isSet == true) && doAction) // unless you do this you will get CS0019 Operator '&&' cannot be applied to operands of type 'bool?' and 'bool'
{
    // Some code that responds to this option
}

While neither behavior can be changed in C# or VB I would think people would preferer the VB semantics. In almost all cases the VB conversion logic does what I expect and in C# I need to work around the conversion logic C# uses. I would personally fight to not change this behavior in VB even if we could

rskar-git commented 4 years ago

Any comparison in the universe can only yield true of false.

Any comparison between numerics can't be nothing. ... In short, comparisons IMHO can't be three-headed monsters.

Yeesh! There is definitely a tremendous amount of misunderstanding going on here.

Law of the Excluded Middle: This is the classic position of logic, where either something either is or something isn't. It's "yes", or it's "no", but it's never "maybe." Only "true", or "false". (https://en.wikipedia.org/wiki/Law_of_excluded_middle) Ask your local physicist on how assuredly that law holds in quantum mechanics. (https://www.npr.org/sections/13.7/2011/01/03/132607500/an-hypothesis-res-potentia-and-res-extensa-linked-by-measurement)

NULL: The major bone of contention here. The first misconception to shake from your head is any idea that NULL is a value too. It is NOT a value. It is a state, and all values are states, but not all states are values. NULL is the state of not having a definitive value. Unfortunately, NULL has two senses: (1) value cannot be obtained; and (2) value is intentionally blank. An OUTER JOIN of tables may result in sense (1). A nullable column allows for moments of sense (2). Unfortunately one might not always know which sense of NULL applies - was it unobtainable? was it simply blank? At least half of the headaches with NULL is in this sort of ambiguity. BTW, that billion dollar mistake (https://en.wikipedia.org/wiki/Tony_Hoare) is mostly about reference nulls, not so much about value nulls.

Three-value Logic: Fine, let's do yes, no, maybe. Let's do true, false, NULL (https://en.wikipedia.org/wiki/Three-valued_logic). Let's do SQL. After that, maybe fuzzy sets.

Nothing: This keyword is specifically a stand-in for the default value of an instance; really it's much the same as default is in C#. Near as I can figure, that default invariably is a bundle of bits that are all zeros. C# also has the null keyword, which specifically means the null state of an instance, where it applies; VB does NOT have an analog. The precise meaning of Nothing in code changes with the context. Dim x = Nothing = Nothing results in x getting set to True because the first match made among the overloads for = (equals operator) is that of integer-equals-integer; and the default of Integer is 0.

VB and SQL: We do three-value logic. Let's say we have an x and we have a y. We know what y is, but we have no definitive assessment of x. But the program is running now, and we need quick answers for conditions like "is x equal to y" and "is y less than x". OK, fine, we don't know x, so NULL to both, because we just don't know. Let's say we have a z; we don't know z. Quick answer please, "is x equal to z"? We don't know x and we don't know z, so NULL - really, we just don't know.

C#: Our not knowing is a kind of knowing! We have x, not known, y, that is known, and z, not known. What is "is x equal to y"? That's easy, we don't know x but we know y, so false. What is "is x equal to z"? That's easy, we don't know x AND we don't know z, so true! OK, what is "is x greater than or equal to z"? That's easy, we don't know x AND we don't know z, so false! See that? x == z is true, and x >= z is false. But seriously, the C# idiom works nicely for when NULL is meant in the sense of an intentional blank where == and != is mostly used.

jrmoreno1 commented 4 years ago

@Padanian

Well, we've got a million-line software in vb6 we can't port to vb.net because we used array of controls. Abandoning array of controls was a breaking change.

Arrays of controls are easily implemented in .net, when I last did a vb6 to .net migration, one of the intermediate steps was to create a design time control array. It’s been nearly a decade, so I don’t remember the exact details, but IExtenderProvider was used to create my own control arrays with the properties I needed to make the migration easier.

CyrusNajmabadi commented 3 years ago

Closing out as several language/compiler contributors have pointed out that this would be a major breaking change. It would be very likely to have significant impact on real world projects.

VBAndCs commented 3 years ago

This does't cause a runtime error now (Option strict Off):

Dim brandId As Integer? = 0
        Dim typeId As Integer? = 0
        Dim CatalogBrandId As Integer = 1
        Dim CatalogTypeId As Integer = 2
        Dim f As Boolean = (Not brandId.HasValue OrElse CatalogBrandId = brandId) AndAlso
            (Not typeId.HasValue OrElse CatalogTypeId = typeId)

        Console.WriteLine(f)

But it still doesn't build when Option strict On, which is reasonable. @CyrusNajmabadi Has anything changed in the compiler since I published this issue?

CyrusNajmabadi commented 3 years ago

No that i'm aware of.