How to return any variable type?

Just starting out? Need help? Post your questions and find answers here.
c4s
Addict
Addict
Posts: 1981
Joined: Thu Nov 01, 2007 5:37 pm
Location: Germany

How to return any variable type?

Post 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?
If any of you native English speakers have any suggestions for the above text, please let me know (via PM). Thanks!
User avatar
TI-994A
Addict
Addict
Posts: 2698
Joined: Sat Feb 19, 2011 3:47 am
Location: Singapore
Contact:

Re: How to return any variable type?

Post 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.
Texas Instruments TI-99/4A Home Computer: the first home computer with a 16bit processor, crammed into an 8bit architecture. Great hardware - Poor design - Wonderful BASIC engine. And it could talk too! Please visit my YouTube Channel :D
said
Enthusiast
Enthusiast
Posts: 342
Joined: Thu Apr 14, 2011 6:07 pm

Re: How to return any variable type?

Post 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$

c4s
Addict
Addict
Posts: 1981
Joined: Thu Nov 01, 2007 5:37 pm
Location: Germany

Re: How to return any variable type?

Post 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? :?
If any of you native English speakers have any suggestions for the above text, please let me know (via PM). Thanks!
User avatar
luis
Addict
Addict
Posts: 3893
Joined: Wed Aug 31, 2005 11:09 pm
Location: Italy

Re: How to return any variable type?

Post 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.
"Have you tried turning it off and on again ?"
A little PureBasic review
c4s
Addict
Addict
Posts: 1981
Joined: Thu Nov 01, 2007 5:37 pm
Location: Germany

Re: How to return any variable type?

Post 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.
If any of you native English speakers have any suggestions for the above text, please let me know (via PM). Thanks!
said
Enthusiast
Enthusiast
Posts: 342
Joined: Thu Apr 14, 2011 6:07 pm

Re: How to return any variable type?

Post 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$

User avatar
TI-994A
Addict
Addict
Posts: 2698
Joined: Sat Feb 19, 2011 3:47 am
Location: Singapore
Contact:

Re: How to return any variable type?

Post 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.
Texas Instruments TI-99/4A Home Computer: the first home computer with a 16bit processor, crammed into an 8bit architecture. Great hardware - Poor design - Wonderful BASIC engine. And it could talk too! Please visit my YouTube Channel :D
User avatar
luis
Addict
Addict
Posts: 3893
Joined: Wed Aug 31, 2005 11:09 pm
Location: Italy

Re: How to return any variable type?

Post 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 :mrgreen:


This thread gave me an unhealthy idea -> http://www.purebasic.fr/english/viewtop ... =3&t=55335
"Have you tried turning it off and on again ?"
A little PureBasic review
User avatar
idle
Always Here
Always Here
Posts: 5836
Joined: Fri Sep 21, 2007 5:52 am
Location: New Zealand

Re: How to return any variable type?

Post 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
Windows 11, Manjaro, Raspberry Pi OS
Image
User avatar
luis
Addict
Addict
Posts: 3893
Joined: Wed Aug 31, 2005 11:09 pm
Location: Italy

Re: How to return any variable type?

Post 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
"Have you tried turning it off and on again ?"
A little PureBasic review
c4s
Addict
Addict
Posts: 1981
Joined: Thu Nov 01, 2007 5:37 pm
Location: Germany

Re: How to return any variable type?

Post 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...
If any of you native English speakers have any suggestions for the above text, please let me know (via PM). Thanks!
User avatar
TI-994A
Addict
Addict
Posts: 2698
Joined: Sat Feb 19, 2011 3:47 am
Location: Singapore
Contact:

Re: How to return any variable type?

Post 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.
Texas Instruments TI-99/4A Home Computer: the first home computer with a 16bit processor, crammed into an 8bit architecture. Great hardware - Poor design - Wonderful BASIC engine. And it could talk too! Please visit my YouTube Channel :D
User avatar
TI-994A
Addict
Addict
Posts: 2698
Joined: Sat Feb 19, 2011 3:47 am
Location: Singapore
Contact:

Re: How to return any variable type?

Post 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.
Texas Instruments TI-99/4A Home Computer: the first home computer with a 16bit processor, crammed into an 8bit architecture. Great hardware - Poor design - Wonderful BASIC engine. And it could talk too! Please visit my YouTube Channel :D
User avatar
TI-994A
Addict
Addict
Posts: 2698
Joined: Sat Feb 19, 2011 3:47 am
Location: Singapore
Contact:

Re: How to return any variable type?

Post 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
Texas Instruments TI-99/4A Home Computer: the first home computer with a 16bit processor, crammed into an 8bit architecture. Great hardware - Poor design - Wonderful BASIC engine. And it could talk too! Please visit my YouTube Channel :D
Post Reply