Malfunction - Malfunction

BlitzMax Forums/BlitzMax Beginners Area/Malfunction - Malfunction

dw817(Posted 2016) [#1]
BMX Functions are certainly a powerful thing and a welcome addition to any game programming language.

However, there is one thing I do not understand, and that is how to make a function that can accept ANY type of a variable, IE:
Strict

Print additno(2,3)
Print addit$("2","3")

Function additno(a,b)
  Return a+b
EndFunction

Function addit$(a$,b$)
  Return a+b
EndFunction


Is there a way to be able to call a single function and you can write code to change the results based upon whether you are using a number or a string for an argument in calling it ?

Function FuncSel(addit$(a$,b$),addit(x,y))
. . .
EndFunction


GW(Posted 2016) [#2]
you can try casting your string arguments to int via: Int(a)
but this will silently fail if it cant be converted.
Bmax is not a dynamically typed scripting language, it's a statically typed compiled language.


Derron(Posted 2016) [#3]
Ints should auto-convert to string, so "addit" should do for both cases

Print addit(2,3)
Print addit("2","3")

Function addit$(a$,b$)
  Return a+b
EndFunction


If you have "types" as param, you will have to use the ":object" param type and do some casting ("if TMyType(obj) then ...")


bye
Ron


Floyd(Posted 2016) [#4]
I don't see any way to make BlitzMax behave in the desired fashion.

Every value has a type. The + operator can operate on numbers and strings, but the meaning varies with the type. It is addition for integers, concatenation for strings.

BlitzMax autoconverts the native types, but the results are not what you want. Having addit(2,3) produce "23" for integers 2,3 is really not doing what you expect.

Without being able to detect the type of variables we don't know what + means. It depends not only on the string versus integer issue already seen but even on different kinds of numbers.

If I tell you that x,y are numbers and x=30000 and y=40000 then what is x+y? You don't know without more information.

Just one example, if both are Integer then the answer is 70000. But it is 4464 if both are Short.


dw817(Posted 2016) [#5]
("if TMyType(obj) then ...")

Ron, this is intriguing. Can you please post an example of determining the difference between a string array and a single numeric integer ?

If that's too difficult to solve, then the ability to determine the difference between a string and an integer in the main function.


Derron(Posted 2016) [#6]
number are no types in Blitzmax.

This is the reason why

local s:string = ""
local i:int = 0
If not s then print "s is empty"
If not i then print "i is false/0"

works. They do not have real "null" values.


So the problem is then eg. for value "integer 0" - if you think of "0" being the "integer null", then a converted int-to-string would be "" (as this is the "string null"). But of course it is converted to "0".
Same happens when converting a string to an int... the result is, that you cannot cast To and then Back without trouble.

SuperStrict
Framework Brl.StandardIO

Local i:Int = 0
Local s:String = ""
Local s2:String = "0"


Print "i as string: ~q"+String(i)+"~q"
Print "i as int from string: ~q"+Int(String(i))+"~q"

Print "s as int: ~q"+Int(s)+"~q"
Print "s as string from int: ~q"+String(Int(s))+"~q"

Print "s2 as int: ~q"+Int(s2)+"~q"
Print "s2 as string from int: ~q"+String(Int(s2))+"~q"

Print "s2 is the same as s? So ~q"+s+"~q = ~q"+s2+"~q ?"



If numbers would be objects, then could check for "= null" after some casts. This is not the case here.


bye
Ron


dw817(Posted 2016) [#7]
Erm ... I got all zeroes running your code, Ron. Shouldn't a few of those results be one or greater - are you doing a logic check on the variable TYPE ?

The greatest formulae in the world mean naught when both input and results are zero. :)


Yasha(Posted 2016) [#8]
The convention for universal arguments (as established by the BRL.Reflection module) is to type them as Object, and convert numbers to and from strings as they enter and exit the function. Strings are a subclass of Object, and can thus be passed like an array or any other user-defined type.

Numbers in BlitzMax are represented as hardware numbers for maximum performance, and aren't interoperable at all with the Object types. You can only convert them to a string represention, or hide them inside a "box" object (boxes are better for performance but sadly aren't the convention).


The way languages like Lua, Python etc. do it - letting numbers be objects as well - is arguably better for high-level programming (it's a pretty big weakness of a "user-friendly" language to make the programmer think about whether they want a hardware int or float and what fixed-width it should have), but dramatically inferior for performance. That's just the choice Mark made, and it can't be undone now because it's absolutely fundamental to the structure of the language.


I think you also have the logic backwards in the above posts. Once a number has been converted to a string, you can't query it to tell if it "is" a number or a string, because it's been converted - there's only a string now. A function that truly supports universal arguments is parametrically polymorphic: it does the same thing regardless of argument type. This is what you'll get if you try to pass strings and numbers-as-strings into the same function.

You could use argument-pack objects:
Function generic:Object(args:Object)
    If Int[](args) Then Return generic_Int(Int[](args)) Else Return generic_String(args)
End Function

generic([1, 2, 3])
generic("foo")

If your arguments are all the same type then an array will do, which gives you the convenient syntax above. Otherwise you'd have to use a custom object with a constructor, which is basically as unwieldy as having separate functions anyway.


Derron(Posted 2016) [#9]
They all there zero - which was part of what I wanted to show off (int("") = int("0")).


@yasha
Thanks (again and again) for your elaboration.


bye
Ron


dw817(Posted 2016) [#10]
Yasha, I tried your code - trying to learn this. It crashed with an error.

Compile Error - Identifier 'generic_int' not found

The only thing I understand about the polymorphing is, if the string has a number in it, it can be interpreted as a number.
a$="23.7" ' displays "23.71"
b=24.8 ' display "241"
plusone a$
plusone b

Function plusone(v$)
Print v+1
EndFunction
You see how tricky this can get.


Yasha(Posted 2016) [#11]
Yeah my intention was that generic_Int and generic_String were specialization functions defined elsewhere.

There's no polymorphic behaviour in your code there though. Both of those are being interpreted as pure strings - 24.8 being assigned to an integer variable is first clipped to 24, then "1" appended to "24", just the same as "1" is appended to "23.7".

One thing you might be missing here - although you can, after a fashion, work out some tricks with function calls, there is no way to make the + operator ever do more than one thing at any given location in the source. In the above code, it is string concatenation and it will always be string concatenation regardless of the arguments. BlitzMax has no notion of operator overloading or polymorphism, so it has to decide at compile-time whether + is integer addition, float addition, string concatenation or array concatenation and then its purpose is fixed forever.

So while a string can or can not have a valid numeric interpretation, whether that interpretation gets used or not is fixed for every given location, regardless of the content of the string. (Every non-numeric string can be read as a zero.) This is in addition to the fact that there is no way to inspect a string and know if it was "originally" a number, or was always a string that happened to only contain digits.

(NB that the BRL convention to use strings for numbers I mentioned above actually does the opposite of what you want, in that it requires you to explicitly write the conversion operations. So there isn't a magic third way of doing things used by the reflection module that you can imitate.)


dw817(Posted 2016) [#12]
Yasha, I guess the question I need to ask, how can I call an array using OBJECT and be able to not only determine which data is incoming but also how to read it back and make use of it in said function as well ?


Yasha(Posted 2016) [#13]
You can use downcasting to both check if an object is an instance of a given type, and also to convert it to that type, rendering the data accessible. Since the type of an array includes the type of its elements, this is the simplest (not necessarily the best) way to tag objects with the type of numeric primitives.

An expanded version of the code above might look like this:

Type Foo
End Type

Local a:Int = 12, b:Float = 3.4, c:String = "ef", d:Foo = New Foo

generic([a, 3, 4])      ' -> "received integers 12 3"
generic([b, 5.5, 6.6])  ' -> "received floats 5.5 6.6"
generic(c)              ' -> "received string 'ef'"
generic([c, "d"])       ' -> "received strings"
generic(d)              ' -> "received a Foo"
generic([d, New Foo])   ' -> "received some Foos"

Function generic(args:Object)
    Select args
        Case Int[](args)
            Local iargs:Int[] = Int[](args)
            Print "received integers " + iargs[0] + " " + iargs[1]
        Case Float[](args)
            Local fargs:Float[] = Float[](args)
            Print "received floats " + fargs[0] + " " + fargs[1]
        Case String(args)
            Local s:String = String(args)
            Print "received string '" + s + "'"
        Case String[](args)
            Print "received strings"
        Case Foo(args)
            Print "received a Foo"
        Case Foo[](args)
            Print "received some Foos"
    End Select
End Function


So as you can see, the syntax for downcasting is the same as any other type conversion. If a cast fails, the result is Null; a simple pattern I'm using above is comparing the object as-passed to the converted version of itself - assuming Null isn't passed as an actual argument, all of the wrong casts will compare unequal as a result.

You can then put the conversion in a named variable for easy access once you've established what it is, or pass it directly to another function that expects the right type.


dw817(Posted 2016) [#14]
Now this code I can understand ! :D

I'm seeing a possible error here though, Yasha:
Strict
Local a=12
generic([a,3,4])

Function generic(args:Object)
    Select args
        Case Int[](args)
            Local iargs:Int[] = Int[](args)
            Print "received integers " + iargs[0] + " " + iargs[1]
    End Select
End Function
The results are only 12 and 3. How do you set it so it will display all elements, including if it were an array ?

Also, adding this to locals above:
Local cards:Byte[52]
and
cards[4]=17
generic(cards)
Reveals blank.


Yasha(Posted 2016) [#15]
wrt. the cards array, that's because there is no test for a Byte[] so it falls through untouched. You need a check for everything you want to handle, or it won't handle it. Unlike the numeric types they're based on, arrays don't allow direct conversion (you can cast an Int to a Byte but not an Int[] to a Byte[] - the Byte[] case therefore needs its own branch).

To handle all of the arguments, you need to know how many there are. You can't know this at compile-time, and obviously statically-written code like the above can only handle one number of elements... if you want to handle argument arrays of different lengths, you'll have to write in a different way that also checks the length and then presumably involves a loop of some kind, since... well, you can't handle varying numbers of things with a fixed set of named variables. (You can feed a fixed set of variables with a variable-length input, but have to be prepared for it to go over or under the required amount and possibly have some default values ready.)

At that point I would definitely just use `generic` itself as a dispatcher that checks and refines the type and then passes values on to other functions that handle the more complex work. Mixing dispatch and large chunks of functionality gets messy.


dw817(Posted 2016) [#16]
Yasha, it's not that difficult. There is a useful command in BlitzMAX called LEN(). It will either return the length of a string or the number of indices in an array thus:
Strict
Local dice:Byte[6],i
dice[1]=3
dice[3]=2
For i=0 Until Len(dice)
  Print i+" "+dice[i]
Next


Now using EACHIN it should be possible to extract each item out in question from an input array, perhaps a pointer ? Or the method you are using via Local iargs:Int[] = Int[](args) ?


Yasha(Posted 2016) [#17]
Extract how? Not seeing what the question is here. Once you're looping over the array... there are your values: you can use them directly.


dw817(Posted 2016) [#18]
Let me recall the original code, Yasha:



In this part of the code:
        Case Int[](args)
            Local iargs:Int[] = Int[](args)
            Print "received integers " + iargs[0] + " " + iargs[1]
Is there a way of printing out all values that occur in an array thus:
Local dice:Byte[6]
dice[2]=6
dice[4]=2
generic(dice)
Where the results would be:

received integers:
0
0
6
0
2
0



TomToad(Posted 2016) [#19]
Dice in your example are not integers. They are bytes. You would need to check for byte arrays and loop over them
Local dice:Byte[6]
dice[2]=6
dice[4]=2
generic(dice)
Function generic(args:Object)
    Select args
        Case Byte[](args)
            Local iargs:Byte[] = Byte[](args)
            Print "received bytes "
			For Local i:Byte = EachIn iargs
				Print i
			Next
    End Select
End Function



dw817(Posted 2016) [#20]
Ah, excellent, Tom ! I will be honest, I think the main reason I want to learn this is I'm lazy and want to be able to call a single function to do a task and change its behavior based on the data entered.

Still, the ability to call a function with ANY type of data and act upon it is a marvel indeed ! I have no doubt this will be useful in the future.

Thank you, Yasha and Tom, for showing me this ! :)

OK, now I'm stuck on retrieving a single integer:
  Case foo(args)
    Local iarg:Int=Int(args)
    Print iarg[0]



TomToad(Posted 2016) [#21]
That is because foo is not an Int. Foo is a Type. If args is an object of type Foo, then the case will succeed and you can retrieve the reference to the Foo object by casting args.

Local Fooarg:Foo = Foo(args)

Then you can access any fields and methods contained within Foo

Fooargs.x = 10

But the above example does not define any fields or methods for Foo so there are none to access.


dw817(Posted 2016) [#22]
Well I was understanding FOO was required because of testing for an INTEGER. If you try this with Yasha's code: generic(a) The results returned are: Received a foo.

Where a is defined in his code as an integer. So color me confused. :)


Yasha(Posted 2016) [#23]
Ah, you're not using Strict/Superstrict mode.

Don't do that. Always use Strict or Superstrict. What's happening right now is that BlitzMax is implicitly trying to convert between objects and integer "handles". This is a compatibility "feature" for older code from Blitz3D/Plus; it's pretty much guaranteed to never do what you want (e.g. the above example making no sense whatsoever). It can also cause memory to leak through the happy combination of silently creating references to an object that then need to be manually released.

(it will not help you in your goal of achieving genericity, as it just adds more confusion: now you can't tell "true" numbers from objects either; as you've seen above, it can create links basically at random)

Most BlitzMax users use SuperStrict 100% of the time and the Strict rules are normally assumed to go without saying. Int handles and Release are not a part of most people's world. The NG compiler doesn't even support non-Strict mode.


dw817(Posted 2016) [#24]
Yasha in my code I always use Strict, try to. But you are saying it is critical to this code ?

I'm not liking SuperStrict as it requires me to define integers. Bad enough you can't mix a and a$ as they are different variables in other languages.

Check the codes I've written in the past, Yasha. You will see I always use strict, even when not needed.

I am hearing about this NG compiler. What does it do the standard BMX does not, and does it hold a superior IDE ?


Matty(Posted 2016) [#25]
I don't know if BlitzMax supports this but many languages support the ability to declare the same function name with different argument lists/types such that the relevant one gets used depending on the variables passed to it...

eg in java you would do this:

public int addnumbers(int a, int b)
{
return a + b;
}
public int addnumbers(String a, String b)
{
try
{
return Integer.decode(a) + Integer.decode(b);
}
catch(Exception e)
{
//do stuff here for naughty boys who try to pass non numeric strings through
}
}
public int addnumbers(float a,float b)
{
return (int)(a + b);
}
//and if you really wanted to be silly about it
public int addnumbers(String a,int b)
{
try
{
return Integer.decode(a) + b;
}
catch(Exception e)
{
//well...what were you expecting ;-)
}
}


excuse the lack of proper indentation - when I type directly into the forum form box if I press tab I don't get a tab I instead get the focus redirected to the next field..... gah!


Brucey(Posted 2016) [#26]
I am hearing about this NG compiler. What does it do the standard BMX does not

It adds native 64-bit compilation, as well as ARM (iOS, Android, ARM Linux/Pi). Adds new primitive types, UInt, ULong, size_t. Adds Interfaces.

Doesn't (yet) support method overloading, or operator overloading...


TomToad(Posted 2016) [#27]
@Matty:
That is called function overloading. BlitzMAX does not support it. You need a seperate name for every type of parameter passed.
Function AddFloat:Float(a:float, b:Float)
Function AddInt:Int(a:Int, b:Int)
Function AddString:String(a:String, b:String)
etc...



dw817(Posted 2016) [#28]
Brucey, could you please point me to any source code (preferably a full game) written in NG that was compiled for Android platform ?


Derron(Posted 2016) [#29]
My game runs on android (but of course is not optimized for touch input).

Dunno if the latest changes to BCC need much adjustments (should give it a try).

Sources are available there:
https://github.com/GWRon/TVTower

(currently with NG you wont have sound output as I disabled it until the maxmod2.mod-modules are compileable there).

I would call it a "bigger game" (albeit still not finished).
It might be needed to adjust the Renderer to use the SDL variant.

If you are interested, Brucey might drop some lines what adjustments would be needed (he once run it on a mobile device).


A more simpler but also full game is "Digesteroids" (included in the samples folder of BlitzMax). This was also something Brucey demonstrated as "running on android".

So if your intention is to get something "gamy" running via NG on android, try out Digesteroids first. If you are more interested to see if "more complex things" are running with NG, then try out my game.


bye
Ron


dw817(Posted 2016) [#30]
There is one thing I'm curious of, Ron. I know with a cellphone or handheld platform you can put two fingers on the screen at the same time and move them and it will register their position and pressure.

Since that is a bit like moving or clicking the mouse, how does it do two or more elements of input this way ?


Derron(Posted 2016) [#31]
dw817:
how does it do two or more elements of input this way ?


I do not understand what you mean (language wise).

If your do a multi-touch, then you get multiple "MouseDown(x) = true" (finger 1: x = 1, finger 2: x=2 ...). So a right click is happening when 2 fingers are down...

I am not sure whether Brucey already added some SDL-touch-functions (swipe and other gestures).


bye
Ron


dw817(Posted 2016) [#32]
Hi Ron. But there is only one MouseX() and MouseY() routine. How can a cellphone determine two fingers, not presses but POSITION ?

Code example, if possible, please.


Derron(Posted 2016) [#33]
Myself:
I am not sure whether Brucey already added some SDL-touch-functions (swipe and other gestures).


This then includes functionality for positions of the other touch "devices" (aka fingers - in most cases).


bye
Ron