Bug? 1 <> 1?

MrKim

Well-Known Member
Licensed User
Longtime User
I would call this a bug.


/this is the line of code that puts in the map:
B4X:
    M.Put("Jr_Released", Crsr.GetInt("Jr_Released"))
IT is an SQL Server cursor.
I was using the cursor directly and it worked but since I was also saving the data decided to set the data using the map.
This was the workaround:
B4X:
    Dim X As Int = M.Get("Jr_Released")
    If X = 1 Then
        SB.Append("HOT")
        T.Style = ";-fx-control-inner-background:rgba(255,93,84,1)"
.
.
.
But still, it should work.
 

MrKim

Well-Known Member
Licensed User
Longtime User
But the watch list shows them as being the same. Isn't that a compiler result? Is it casting the result? I am not an expert in this area, I am just a dumb database programmer trying to get a job done. I would understand an error being thrown that I need to convert a data type but to just blithely continue on with an incorrect result is wrong. If the compiler does not know the type it should say so. An SQL Server int is 4 bytes signed -2,147,483,648 to 2,147,483,647 the same as B4X.
 

Erel

B4X founder
Staff member
Licensed User
Longtime User
At compilation time the compiler doesn't know the type of object that will be returned from Map.Get as it returns an Object which is the base of all types. The debugger shows you the actual value.

Check this example:
B4X:
Dim d As Double = 1
Dim m As Map = CreateMap("test": d)
Log(m.Get("test") = 1) 'fails
Log(1 = m.Get("test")) 'works
The reason that the second one works is that the compiler "knows" that it needs to make a numerical comparison. This is not the case with the first one.
 

MrKim

Well-Known Member
Licensed User
Longtime User
1. Got it, Is that why maps are so fast? They aren't analyzing? Just storing bytes? I am always looking for speed, but there is always a tradeoff. It is easier to validate everything early, but expensive. 90% of data is just looked at. Better to validate when you actually use it.

Still, but the watch list sees them both the same, so why are they not the same? They are even the same data type..

One of the reasons for using VB has always been its ability to coerce data types.
In VBA Dim X As Long, Y As Byte, Z As String X = 1 Y = 1 Z = 1 If X = Y Then MsgBox "True" Else MsgBox "False" If X = Z Then MsgBox "True" Else MsgBox "False" If Y = Z Then MsgBox "True" Else MsgBox "False" Z = "1" If X = Z Then MsgBox "True" Else MsgBox "False" If Y = Z Then MsgBox "True" Else MsgBox "False" All of these statements return true
Even in Microsoft SQL Server
SQL:
DECLARE @X int = 1, @Y tinyint = 1, @Z nvarchar(5) =  1

if @X = @Y select 'true' else select 'false'
if @X = @Z select 'true' else select 'false'
if @Y = @Z select 'true' else select 'false'

SET @Z =  '1'
if @X = @Z select 'true' else select 'false'
if @Y = @Z select 'true' else select 'false'


All of these things return true.
One of the Beauties of VB has always been not having to wrestle with details like this. The compiler just takes care of them.
Perhaps a compiler Switch that could coerce values so we don't have to deal with them?
I LOVE the control that B4X gives me, but at the same time it is taking me at least ten times longer to write comparable code than it does in VBA,
Edit:
Granted, a lot of that is the learning curve but still, it would be nice if B4X could do better with data types - and NULLS! - I will start another thread on that one.
 
Last edited:

Sandman

Expert
Licensed User
Longtime User
I'm sure this is obvious to you, knowing the internals of it all. But for me, this is so far from obvious that I would classify it as a bug. Don't get me wrong, I do understand your explanation, and I do understand it's not a bug. But that is the point - I do need an explanation to understand it, because I have no clue about the inner workings of the compiler and just looking at it I would say that if A equals B then B should also equal A. But, as you have explained, that is not always the case.

Recently you started an awesome thread about code smells. I would suggest starting another thread, perhaps titled something like "things that might not be obvious but important to understand". This would make a fine entry there.
 

Erel

B4X founder
Staff member
Licensed User
Longtime User
Got it, Is that why maps are so fast?
It has nothing to do with it.

The VBA code you posted will also work in B4X. The case with Map is different. The type in this case is not known.

You haven't posted the full code. The compiler cannot know in the first case that you are making a numerical comparison so it treats 1.0 (double) and 1 (int) as different values.

We are talking about an edge case here that happens because you are comparing the value directly from the map.
 

Erel

B4X founder
Staff member
Licensed User
Longtime User
Perhaps a compiler Switch that could coerce values so we don't have to deal with them?
Granted, a lot of that is the learning curve but still, it would be nice if B4X could do better with data types - and NULLS! - I will start another thread on that one.
You are making claims for things that you haven't tested...

Try it:
B4X:
    Dim X As Long, Y As Byte, Z As String
    X = 1
    Y = 1
    Z = 1
    If X = Y Then Log("True") Else Log( "False")
    If X = Z Then Log( "True") Else Log( "False")
    If Y = Z Then Log( "True") Else Log( "False")
    Z = "1"
    If X = Z Then Log( "True") Else Log( "False")
    If Y = Z Then Log( "True") Else Log( "False")

It returns true for all cases.
 

LucaMs

Expert
Licensed User
Longtime User
I apologize for not having read everything; I'm too lazy, especially now.

I understand that the question is not exactly this but... if we all got used to storing a database record in a class or at least in a custom type, we would have no problems of that kind.
(Because:
Dim X As Int = M.Get("Jr_Released")
X would be a "field" of a class or custom type).
 

OliverA

Expert
Licensed User
Longtime User
Please ignore: has nothing to do with the issue. Leaving it here for stupidity sake.
@MrKim: What does this produce?
B4X:
If Crsr.GetInt("Jr_Released") = 1 then
   Log("ResultSet.GetInt returns a primitive int value")
Else
   Log("ResultSet.GetInt returns an object other than a primitive int. Perhaps an Integer?"
End
 
Last edited:

alwaysbusy

Expert
Licensed User
Longtime User
This has caused me some trouble in the past to find why code doesn't work. But I now understand why as Erel explained it. Hopefully I'll recall it in the future.

A common case where I had this problem is in a select:
B4X:
For i = 0 To Recs.Size - 1
        Dim m As Map = Recs.Get(i)
        Select Case m.Get("linetype") ' <--- is a number
            Case 0
                ...
            Case 100
                ...
            Case 200
                ...
        End Select
next

So this works. But it is not easy to catch this 'bug' in your code. A first glance, the first code looks perfectly fine.
B4X:
For i = 0 To Recs.Size - 1
        Dim m As Map = Recs.Get(i)
        Dim LineType As Int = m.Get("dalinetype") ' first put in an Int
        Select Case LineType
            Case 0
                ...
            Case 100
                ...
            Case 200
                ...
        End Select
next

If Crsr.GetInt("Jr_Released") = 1 then
This might actually be a good idea as an extension of the existing Map keyword. Implementing .GetInt(), .GetDouble(), .GetBoolean(), ... next to the normal Get() etc.

You can of course easily write such a wrapper in B4X yourself, but maybe considering it as an extension of the language in the future can't hurt.

Alwaysbusy
 

OliverA

Expert
Licensed User
Longtime User
I'm sure this is obvious to you, knowing the internals of it all. But for me, this is so far from obvious that I would classify it as a bug.
No, it's not a bug. It relates to this: https://stackoverflow.com/questions/4428774/why-java-does-not-see-that-integers-are-equal and @Erel answered it already with
At compilation time the compiler doesn't know the type of object that will be returned from Map.Get as it returns an Object which is the base of all types.
if A equals B then B should also equal A. But, as you have explained, that is not always the case.
Because when you mix primitives with objects, strange things happen.
B4X:
Log(m.Get("test") = 1) 'fails
Why? Because you are comparing an object value (the m.Get("test")) returned to a primitive. Since one is an object, it cannot be equal to a primitive (they are different types). Casting in Java does not happen on the left hand side of the equality, but on the right.
B4X:
Log(1 = m.Get("test")) 'works
In this case (as stated already above), the compiler sees an int on the left hand side and now casts the right hand side to an int. As long as the right and side can be casted to an int value, the comparison will work (it will blow up if the "test" key does not exist).
When you do a
B4X:
Dim LineType As Int = m.Get("dalinetype") ' first put in an Int
you are telling the compiler to cast the object to an int.
Opinion: This
B4X:
Dim LineType As Int = m.Get("dalinetype") ' first put in an Int
will throw an error if dalinetype key does not exists, since at that point the map will return a Null String, which is a String containing the value "null". This value does not cast to an Int and will cause an exception. Unless you're 100% sure that the key will always exist, use 1) GetDefault, or 2) ContainsKey or, last resort, a try/catch block to avoid that issue (unless you need the app to bomb if the key does not exist).

Update: Once more Ninja'd by @Erel by a few seconds. This wraps it up:
The bottom line is that you should be careful when the left side of a comparison is an Object type. It is safer to cast it yourself or put it on the right side of the comparison.
Update #2: Huh? A post disappeared...
 

Erel

B4X founder
Staff member
Licensed User
Longtime User
I've actually deleted my previous post as the compiler always calls the equals method and should handle cases with two different boxed objects.

B4X goes much further than Java to avoid edge cases with different types. Still there are some edge cases when the types are unknown.

If @MrKim can reproduce it in a small project I would be happy to further investigate it as I wasn't able to reproduce it locally.
 

Sandman

Expert
Licensed User
Longtime User
Update #2: Huh? A post disappeared...
Yeah, strange. I'll just assume it was a minor glitch in the matrix and quote Erels missing post anyway.

The bottom line is that you should be careful when the left side of a comparison is an Object type. It is safer to cast it yourself or put it on the right side of the comparison.
I have a naïve question. Considering the fact that so much of B4X is about shielding us from the strange, inner workings of Java, ObjC and whatnot, would it make sense if the compiler helped us out here? I mean, if the compiler sees a comparison where one side is an object and the other is not, and its safer to put it on the right side of the comparison - couldn't the compiler just switch places so that the object is always is on the right side?

Update: Erel posted something just before I posted this. Not sure how relevant my text is now, leaving it for posterity.
 

OliverA

Expert
Licensed User
Longtime User
If @MrKim can reproduce it in a small project I would be happy to further investigate it as I wasn't able to reproduce it locally.
Hm. So it may be related to JDBC after all (again, guessing). The only reason I thought it may be involved is that I noticed:
Microsoft's JDBC driver uses in Integer value in the getInt method (see https://github.com/microsoft/mssql-.../sqlserver/jdbc/SQLServerResultSet.java#L2310) and the B4J library uses a getObject call on that (see https://github.com/AnywhereSoftware...rc/anywheresoftware/b4j/objects/SQL.java#L551). I'm not Java proficient, so I don't know if just returning the call directly will cast it properly to a int (since the method is defined with an int return value), or if an additional casting is needed (on the B4X side).

Udpate: If this were true, I would consider this a bug in Microsofts's JDBC driver. The additional casting in B4X would just be a cure for such a bug (outside of B4X). Again, very clueless here...
 

Erel

B4X founder
Staff member
Licensed User
Longtime User
I mean, if the compiler sees a comparison where one side is an object and the other is not, and its safer to put it on the right side of the comparison - couldn't the compiler just switch places so that the object is always is on the right side?
It is not possible as it will have other side effects. The left side type has semantic meaning.
B4X:
Log("a" = 1) 'string comparison ("a" = "1") = false
Log(1 = "a") 'numeric comparison => crash
 
Cookies are required to use this site. You must accept them to continue using the site. Learn more…