Page 1 of 2
How to return any variable type?
Posted: Sat Jul 06, 2013 10:56 am
by c4s
With the recent new features of PB and myself being in a productive mood, I finally decided to do some major restructuring / clean-up of my older code.
One thing I want to achieve is to gather a lot of different variables (in my case also known as "settings" or "options") in to a central place, e.g. a structured array. It will include information such as the type (#PB_Byte, #PB_String etc.) and of course the
value itself.
The problem is how to properly deal with the set/get procedures? If I would only allow integer, byte etc. this wouldn't be an issue. However I also want to support float and even string "values".
Long story short, I'm thinking of something like this:
Code: Select all
Procedure SettingsInit()
Settings(#Option1Byte)\Type = #PB_Byte
Settings(#Option4String)\Type = #PB_String
; ...
EndProcedure
Macro SettingsSet(ID, Value) ; Macro, otherwise 'Value' couldn't have any type
Select Settings(ID)\Type
Case #PB_Byte : Settings(ID)\ValueB = Value
Case #PB_String : Settings(ID)\ValueS = Value
EndSelect
EndMacro
Procedure SettingsGet(ID)
Select Settings(ID)\Type
Case #PB_Byte : Value = Settings(ID)\ValueB
Case #PB_String : Value = Settings(ID)\ValueS
EndSelect
ProcedureReturn Value
EndProcedure
SettingsInit() ; Setup of the internals
SettingsSet(#Option1Byte, 123)
SettingsSet(#Option4String, "test")
Byte = SettingsGet(#Option1Byte) ; Should put 123 in 'Byte'
String$ = SettingsGet(#Option4String) ; Should put "test" in 'String$'
Obviously (and unfortunately) this code doesn't work. Any ideas how I can make it work while keeping the syntax as easy as possible?
Re: How to return any variable type?
Posted: Sat Jul 06, 2013 12:35 pm
by TI-994A
c4s wrote:One thing I want to achieve is to gather a lot of different variables (in my case also known as "settings" or "options") in to a central place, e.g. a structured array. It will include information such as the type (#PB_Byte, #PB_String etc.) and of course the value itself.
Hi c4s. Perhaps the PureBasic function TypeOf() might be useful in determining the variable type. It returns an integer with values including #PB_Byte, #PB_Long, #PB_String, etc.
However, you'd probably have to use some common variable type to handle the values, unless the holding structure contains a field for every variable type.
Re: How to return any variable type?
Posted: Sat Jul 06, 2013 1:01 pm
by said
I think you need to isolate the string case, for other value you can rely on PB auto casting ... something like that:
Code: Select all
#Option1Byte = 1
#Option2Long = 2
#Option3Float = 3
#Option4String = 4
Structure myStruct
Type.i
ValueB.b
ValueL.b
ValueF.f
ValueS.s
EndStructure
Global Dim Settings.myStruct(10)
Procedure SettingsInit()
Settings(#Option1Byte)\Type = #PB_Byte
Settings(#Option4String)\Type = #PB_String
; ...
EndProcedure
Macro SettingsSet(ID, Value) ; Macro, otherwise 'Value' couldn't have any type
Select Settings(ID)\Type
Case #PB_Byte : Settings(ID)\ValueB = Value
Case #PB_Long : Settings(ID)\ValueL = Value
Case #PB_Float : Settings(ID)\ValueF = Value
EndSelect
EndMacro
Macro SettingsSetStr(ID, Value)
Select Settings(ID)\Type
Case #PB_String : Settings(ID)\ValueS = Value
EndSelect
EndMacro
Macro SettingsGet(ID, Value)
Select Settings(ID)\Type
Case #PB_Byte : Value = Settings(ID)\ValueB
Case #PB_Long : Value = Settings(ID)\ValueL
EndSelect
EndMacro
Macro SettingsGetStr(ID, Value)
Select Settings(ID)\Type
Case #PB_String : Value = Settings(ID)\ValueS
EndSelect
EndMacro
SettingsInit() ; Setup of the internals
SettingsSet(#Option1Byte, 123)
SettingsSetStr(#Option4String, "test")
;Byte = SettingsGet(#Option1Byte) ; Should put 123 in 'Byte'
;String$ = SettingsGet(#Option4String) ; Should put "test" in 'String$'
SettingsGet(#Option1Byte, Byte) ; Should put 123 in 'Byte'
SettingsGetStr(#Option4String, String$) ; Should put "test" in 'String$'
Debug Byte
Debug String$
Re: How to return any variable type?
Posted: Sat Jul 06, 2013 5:42 pm
by c4s
Thank you two for your suggestions.
TI-994A wrote:you'd probably have to use some common variable type to handle the values, unless the holding structure contains a field for every variable type.
Currently that's the way I've implemented it because I didn't came up with a better solution. It really isn't the best idea to store a couple of variables from which I'm only going to use one. But I'm fine with the little overhead for the sake of easiness.
said wrote:I think you need to isolate the string case
I just found out that when I use a number higher than 255 there will also be compiler error too, because with the macro the high number will be set to the byte variable (at least the compiler thinks so).
It seems that the macro cannot be used at all so I would have to use a set/get procedure for every variable type, not just strings.
That's what I feared of. As soon as I have to take care of special cases, the whole system is getting more complex and that's what I was trying to reduce.
Isn't there maybe some macro magic that can help me out? Or any other ideas on how to get it working?

Re: How to return any variable type?
Posted: Sat Jul 06, 2013 7:40 pm
by luis
Maybe it's not something you will like, but would be really a problem to use a specific Set() and Get() proc for each data type ?
After all you should know what type of data are you storing, and hopefully what are you getting back.
You could still store all the settings together if you can just use strings.
Here is what I mean:
Code: Select all
Structure Item
DataType.i
s.s
EndStructure : Global Dim Settings.Item(10)
Procedure SettingsSetI (ID, Value.i)
Settings(ID)\DataType = #PB_Integer : Settings(ID)\s = Str(Value)
EndProcedure
Procedure SettingsSetS (ID, Value.s)
Settings(ID)\DataType = #PB_String: Settings(ID)\s = Value
EndProcedure
Procedure SettingsSetF (ID, Value.f)
Settings(ID)\DataType = #PB_Float : Settings(ID)\s = StrF(Value)
EndProcedure
; etc. etc.
Procedure.i SettingsGetI (ID)
ProcedureReturn Val(Settings(ID)\s)
EndProcedure
Procedure.f SettingsGetF (ID)
ProcedureReturn ValF(Settings(ID)\s)
EndProcedure
Procedure.s SettingsGetS (ID)
ProcedureReturn Settings(ID)\s
EndProcedure
; etc. etc.
; TEST
SettingsSetI (0, 123)
SettingsSetS (1, "Hello")
SettingsSetF (2, 123.125)
Debug SettingsGetI(0)
Debug SettingsGetS(1)
Debug SettingsGetF(2)
; and if later on you don't know the data type you can check it
For i = 0 To 2
Select Settings.Item(i)\DataType
Case #PB_Integer : Debug SettingsGetI(i)
Case #PB_String : Debug SettingsGetS(i)
Case #PB_Float : Debug SettingsGetF(i)
EndSelect
Next
I know it's not ideal, but maybe it can do ? This way you don't need the init part either, something I didn't like very much.
Re: How to return any variable type?
Posted: Sat Jul 06, 2013 8:09 pm
by c4s
luis, you're right. I had a second thought about the get/set procedures and realized that having one for each type is probably the best (and only possible) solution - although I still think that one for all would be way more elegant!
By the way: I initiate each option with an unique id because once set, it shouldn't be possible to change the type anymore. Also I'm storing some other information for validation purposes, e.g. what values the option can have etc.
Re: How to return any variable type?
Posted: Sat Jul 06, 2013 9:32 pm
by said
Usually i would go as per Luis's suggested procedures! In case you still want your original way (which is somehow more elegant), you can use this (on more try)
Code: Select all
#Option1Byte = 1
#Option2Long = 2
#Option3Float = 3
#Option4Integer = 4
#Option5Double = 5
#Option6String = 6
Structure myStruct
Type.i
mem.b[7] ; to store largest numerical value 8 for double/quad
String.s
EndStructure
Global Dim Settings.myStruct(10)
Procedure SettingsInit()
Settings(#Option1Byte)\Type = #PB_Byte
Settings(#Option2Long)\Type = #PB_Long
Settings(#Option3Float)\Type = #PB_Float
Settings(#Option4Integer)\Type = #PB_Integer
Settings(#Option5Double)\Type = #PB_Double
Settings(#Option6String)\Type = #PB_String
; ...
EndProcedure
Macro SettingsSet(ID, Value)
Select Settings(ID)\Type
Case #PB_Byte : PokeB(@Settings(ID)\mem[0], Value)
Case #PB_Long : PokeL(@Settings(ID)\mem[0], Value)
Case #PB_Float : PokeF(@Settings(ID)\mem[0], Value)
Case #PB_Integer : PokeI(@Settings(ID)\mem[0], Value)
Case #PB_Double : PokeD(@Settings(ID)\mem[0], Value)
EndSelect
EndMacro
Procedure SettingsSetStr(ID, S.s)
Select Settings(ID)\Type
Case #PB_String : Settings(ID)\String = S
EndSelect
EndProcedure
Macro SettingsGet(ID, Value)
Select Settings(ID)\Type
Case #PB_Byte : Value = PeekB(@Settings(ID)\mem[0])
Case #PB_Long : Value = PeekL(@Settings(ID)\mem[0])
Case #PB_Float : Value = PeekF(@Settings(ID)\mem[0])
Case #PB_Integer : Value = PeekI(@Settings(ID)\mem[0])
Case #PB_Double : Value = PeekD(@Settings(ID)\mem[0])
EndSelect
EndMacro
Macro SettingsGetStr(ID, Value)
Select Settings(ID)\Type
Case #PB_String : Value = Settings(ID)\String
EndSelect
EndMacro
SettingsInit() ; Setup of the internals
SettingsSet(#Option1Byte, 123)
SettingsSet(#Option2Long, 12345)
SettingsSet(#Option3Float, 12345.123)
SettingsSet(#Option5Double, 12345678987654.123)
SettingsSetStr(#Option6String, "test")
SettingsGet(#Option1Byte, Byte)
SettingsGet(#Option2Long, Long)
SettingsGet(#Option3Float, Float.f)
SettingsGet(#Option5Double, Double.d)
SettingsGetStr(#Option6String, String$)
Debug Byte
Debug Long
Debug StrF(Float)
Debug StrD(Double)
Debug String$
Re: How to return any variable type?
Posted: Sat Jul 06, 2013 9:56 pm
by TI-994A
c4s wrote:...I still think that one for all would be way more elegant!

Hello again c4s. This method implements your
elegant one-for-all preference. When setting the values, to circumvent the multiple-type issue, strings are passed by address, and numerics are all passed as floats and re-cast into their intended types within the procedure. When getting the values, they are all passed as strings, and can be converted to their respective types using the Val() & ValF() functions if required:
Code: Select all
Structure variableInfo
type.i
byteVal.b
longVal.l
floatVal.f
stringVal.s
EndStructure
Global Dim varInfo.variableInfo(10)
Procedure setValue(index.i, value.f, type.i)
Select type
Case #PB_Byte
varInfo(index)\byteVal = value
Case #PB_Long
varInfo(index)\longVal = value
Case #PB_Float
varInfo(index)\floatVal = value
Case #PB_String
varInfo(index)\stringVal = PeekS(value)
EndSelect
varInfo(index)\type = type
EndProcedure
Procedure.s getValue(index.i)
Select varInfo(index)\type
Case #PB_Byte
ProcedureReturn Str(varInfo(index)\byteVal)
Case #PB_Long
ProcedureReturn Str(varInfo(index)\longVal)
Case #PB_Float
ProcedureReturn StrF(varInfo(index)\floatVal)
Case #PB_String
ProcedureReturn varInfo(index)\stringVal
EndSelect
EndProcedure
setValue(1, 123, #PB_Byte)
setValue(2, 456, #PB_Long)
setValue(3, 789.123, #PB_Float)
setValue(4, @"Hello", #PB_String)
Debug getValue(1)
Debug getValue(2)
Debug getValue(3)
Debug getValue(4)
Hope it's helpful.
Re: How to return any variable type?
Posted: Sat Jul 06, 2013 10:20 pm
by luis
Problem is this way you just moved the explicit conversions from inside the type-specific procs to the extern of a more generic one. Since you still need to convert the return values to store them in the appropriate variables of the right type in the end the interface is still not uniform but in a different way. And the data structure is now larger (you could use a union for the numeric types but wouldn't help much). I understand is more similar to what c4s hoped, but I don't know if it's really an improvement
This thread gave me an unhealthy idea ->
http://www.purebasic.fr/english/viewtop ... =3&t=55335
Re: How to return any variable type?
Posted: Sat Jul 06, 2013 11:23 pm
by idle
using a union is probably the way to go
Code: Select all
Structure uAny
type.i
StructureUnion
a.a
b.b
c.c
u.u
w.w
l.l
i.i
f.f
d.d
q.q
*ptr
EndStructureUnion
s.s
EndStructure
Debug SizeOf(uany)
Global Dim Settings.uany(10)
;set
For a = 1 To 4
Settings(a)\type = #PB_Integer
Settings(a)\i = a
Next
Settings(5)\type = #PB_Float
Settings(5)\f = #PI
For a = 6 To 10
Settings(a)\type = #PB_String
Settings(a)\s = "string " + Str(a)
Next
;enum
For a = 1 To 10
Select Settings(a)\type
Case #PB_Integer
Debug "Int " + Str(Settings(a)\i)
Case #PB_Float
Debug StrF(Settings(a)\f)
Case #PB_String
Debug Settings(a)\s
EndSelect
Next
;strings need to be passed by address in the macro
Macro SetSetting(id,val,type)
Select type
Case #PB_String
settings(id)\s = PeekS(val)
Default
settings(id)\q = val
EndSelect
EndMacro
;just use the native type
Macro GetSetting(ID,type)
settings(id)\type
EndMacro
SetSetting(1,@"hello",#PB_String)
SetSetting(2,123,#PB_Byte)
s1.s = GetSetting(1,s)
Debug s1
b1.b = GetSetting(2,b)
Debug b1
Re: How to return any variable type?
Posted: Sat Jul 06, 2013 11:31 pm
by luis
Even if it's nice, I still think it doesn't change much.
This time it's not the return value that need to be converted, but it's the GetSetting() proc/macro requiring the name of the field. So again the interface is not uniform, and doesn't give a real benefit vs the multiple typed-procs.
The "s", "i", "b" strings are moved from the name of the proc to one of its params.
JMHO
Re: How to return any variable type?
Posted: Sat Jul 06, 2013 11:35 pm
by c4s
@said & @TI-994A & @idle
Thanks again for your suggestions! Really appreciated as it gives my another perspective on the problem. Especially idle's version looks interesting as it's almost doing what I want.
Written before I've seen idle's code: However, for now I decided to go with a get/set procedure for each type because for me it's even more important to be able to get the value as a direct result:
Code: Select all
; Would be cool:
Byte.b = SettingsGet(#OptionDoThis)
String$ = SettingsGet(#OptionFilePath)
; Also ok, as there is no other way:
Byte.b = SettingsGetB(#OptionDoThis)
String$ = SettingsGetS(#OptionFilePath)
@luis
I really like that feature request you've posted. Would solve my problem while keeping everything PB-like...
Re: How to return any variable type?
Posted: Sun Jul 07, 2013 10:52 am
by TI-994A
luis wrote:...you still need to convert the return values to store them in the appropriate variables of the right type in the end the interface is still not uniform but in a different way. And the data structure is now larger...
Hi luis. You're right, and apparently, that's not what he needed either. My thought was that since the original values were already directly accessible from the global structure, he only needed a convenient way to read the values, in which case the string solution would have sufficed. And although the structure could become quite bloated, especially with the addition of yet more data types, I usually prefer to keep numericals in properly typed variables, maintaining better accuracy, and avoiding slow string and conversion operations where possible.
Re: How to return any variable type?
Posted: Sun Jul 07, 2013 10:53 am
by TI-994A
c4s wrote:...it's even more important to be able to get the value as a direct result:
Code: Select all
; Would be cool:
Byte.b = SettingsGet(#OptionDoThis)
String$ = SettingsGet(#OptionFilePath)
Hi c4s. The main aim of my earlier implementation was to get the values from the structure
without having to know or indicate the data type when calling the getValue() function. However, since it returned only a string value, while you clearly need the actual value, perhaps this method might prove more in line with your
one-for-all preference. It uses a set of global variable types that would be assigned the corresponding index value of the data structure:
Code: Select all
EnableExplicit
Enumeration
#OptionDoThis
#OptionFilePath
#OptionDoLong
#OptionDoFloat
#OptionFloat2
#OptionString2
EndEnumeration
Structure variableInfo
type.i
byteVal.b
longVal.l
floatVal.f
stringVal.s
EndStructure
Global Dim varInfo.variableInfo(10)
Global byteVal.b, longVal.l, floatVal.f, stringVal.s
Procedure clearVals(); good practice, but not required
byteVal = 0
longVal = 0
floatVal = 0
stringVal = ""
EndProcedure
Procedure setValue(index.i, value.f, type.i)
Select type
Case #PB_Byte
varInfo(index)\byteVal = value
Case #PB_Long
varInfo(index)\longVal = value
Case #PB_Float
varInfo(index)\floatVal = value
Case #PB_String
varInfo(index)\stringVal = PeekS(value)
EndSelect
varInfo(index)\type = type
EndProcedure
Procedure getValue(index.i)
clearVals()
Select varInfo(index)\type
Case #PB_Byte
byteVal = varInfo(index)\byteVal
Case #PB_Long
longVal = varInfo(index)\longVal
Case #PB_Float
floatVal = varInfo(index)\floatVal
Case #PB_String
stringVal = varInfo(index)\stringVal
EndSelect
EndProcedure
setValue(#OptionDoThis, 123, #PB_Byte)
setValue(#OptionDoLong, 456, #PB_Long)
setValue(#OptionDoFloat, 789.123, #PB_Float)
setValue(#OptionFilePath, @"Hello", #PB_String)
setValue(#OptionFloat2, 987.654321, #PB_Float)
setValue(#OptionString2, @"Another String", #PB_String)
;according to your requirements, since
;the variable TYPE for getValue() is
;known beforehand, the value could simply
;be assigned to a common global variable
;to be used directly without any conversion
getValue(#OptionDoThis): Debug byteVal
getValue(#OptionDoLong): Debug longVal
getValue(#OptionDoFloat): Debug floatVal
getValue(#OptionFilePath): Debug stringVal
getValue(#OptionString2): Debug stringVal
getValue(#OptionFloat2): Debug floatVal
Just another approach to your final solution.
Re: How to return any variable type?
Posted: Sun Jul 07, 2013 12:56 pm
by TI-994A
Here's an alternate method, utilising only macros, with no type conversions whatsoever:
Code: Select all
EnableExplicit
Enumeration
#OptionDoThis
#OptionFilePath
#OptionDoLong
#OptionDoFloat
#OptionFloat2
#OptionString2
EndEnumeration
Structure variableInfo
type.i
byteVal.b
longVal.l
floatVal.f
stringVal.s
EndStructure
Global Dim varInfo.variableInfo(10)
Define byteVal.b, longVal.l, floatVal.f, stringVal.s
Macro clearVals(); good practice, but not required
byteVal = 0
longVal = 0
floatVal = 0
stringVal = ""
EndMacro
Macro setValue(index, value, type)
CompilerSelect type
CompilerCase #PB_Byte
varInfo(index)\byteVal = value
CompilerCase #PB_Long
varInfo(index)\longVal = value
CompilerCase #PB_Float
varInfo(index)\floatVal = value
CompilerCase #PB_String
varInfo(index)\stringVal = value
CompilerEndSelect
EndMacro
Macro getValue(index, value)
clearVals()
CompilerSelect TypeOf(value)
CompilerCase #PB_Byte
value = varInfo(index)\byteVal
CompilerCase #PB_Long
value = varInfo(index)\longVal
CompilerCase #PB_Float
value = varInfo(index)\floatVal
CompilerCase #PB_String
value = varInfo(index)\stringVal
CompilerEndSelect
EndMacro
setValue(#OptionDoThis, 123, #PB_Byte)
setValue(#OptionDoLong, 456, #PB_Long)
setValue(#OptionDoFloat, 789.123, #PB_Float)
setValue(#OptionFilePath, "Hello", #PB_String)
setValue(#OptionFloat2, 987.654321, #PB_Float)
setValue(#OptionString2, "Another String", #PB_String)
getValue(#OptionDoThis, byteVal): Debug byteVal
getValue(#OptionDoLong, longVal): Debug longVal
getValue(#OptionDoFloat, floatVal): Debug floatVal
getValue(#OptionFilePath, stringVal): Debug stringVal
getValue(#OptionString2, stringVal): Debug stringVal
getValue(#OptionFloat2, floatVal): Debug floatVal