Initializing a static variable in VBA with non-default value - vba

Static variables in VBA are simple enough:
Public Sub foo()
Static i As Integer
i = i + 1
Debug.Print i
End Sub
outputs (when called multiple times):
1
2
3
...
The problem is, VBA does not support initializing a variable on the same line as the declaration (not counting using : to put two lines on one):
Public Sub foo()
Dim i As Integer = 5 'won't compile!
Dim j As Integer
j = 5 'we have to do this instead
End Sub
This clashes with static variables:
Public Sub foo()
Static i As Integer 'we can't put an initial value here...
i = 5 'so this is how we'd usually initialize it, but...
i = i + 1
Debug.Print i
End Sub
You can probably see what happens - The very first thing the variable does every time foo is called is set itself back to 5. Output:
6
6
6
...
How can you initialize a static variable in VBA to a value other than its default? Or is this just VBA dropping the ball?

One way to do this if you want to keep the static semantics and not switch to a global is to sniff the default value and then set the initial condition:
Static i As Integer
if (i = 0) then i = 5
Safer alternative would perhaps be
Static i As Variant
if isempty(i) then i = 5
Or
Public Sub foo(optional init as boolean = false)
Static i As Integer
if init then
i = 5
exit sub
endif
You could probably also create a class with a default property and use class_initialize but that's probably a bit over-fussy.

I had the same issue in VB6, where it's exactly the same, and I like the Microsoft recommendation most:
Sub initstatic ()
Static Dummy, V, I As Integer, S As String
' The code in the following if statement will be executed only once:
If IsEmpty(Dummy) Then
' Initialize the dummy variant, so that IsEmpty returns FALSE for
' subsequent calls.
Dummy = 0
' Initialize the other static variables.
V = "Great"
I = 7
S = "Visual Basic"
End If
Print "V="; V, "I="; I, "S="; S
' Static variables will retain their values over calls when changed.
V = 7
I = I + 7
S = Right$(S, 5)
End Sub

I solved it as follows using a static boolean to indicate if you are entering the function for the first time. This logic should work for other situation as well, i think
Private Sub Validate_Date(TB as MSForms.TextBox)
Static Previous_Value as Date
Static Not_First_Time as Boolean
if Not_First_Time = False Then
Previous_Value = Now
Not_First_Time = True
endif
if IsDate(TB.Value) = False then TB.Value = Previous_Value
Previous_Value = TB.Value
End sub

The use of a Boolean to flag something as already initialized functions correctly in normal use but has an unexpected side effect when using the debugger. bIsInitialized is NOT reset to False when the VBA projuect is re-compiled. When the initialization code (or the constants the code uses) is changed changed, the thing being initialized will not be re-initialized while using the debugger.
One work-around is to set a breakpoint at the statement "If (Not bIsInitialized) Then" and add bIsInitialized as a watch variable. When the breakpoint is reached the first time, Click on the value and change it to false, remove the breakpoint and use F5 to continue. There may be a better work-around that uses something that is reliably reset by recompiling the project, but since the documentation for VBA says that the Boolean would be re-initialed after going out of context and all code stopping, there's no way to know if that behavior was version dependent. Not reinitializing the block of memory associated with the routine appears to be a performance optimization.
Static bIsInitialized as Boolean
Static something_time_consuming_to_initialize
If (Not bIsInitialized) Then
initialize something_time_consuming_to_initialize
bIsInitialized = True
End If

Related

Class variable as the counter of a For loop in VBA

I have a class module called MyClass, with a public integer in it:
Public i as Integer
When I try to use this variable in a For loop like so:
Dim MyInstance as MyClass: Set MyInstance = New MyClass
For MyInstance.i = 1 To 10
Debug.Print "Hello"
Next
I get the error: Variable required. Can't assign to this expression
I have consulted the help page but cannot see how it applies to my case. The relevant fragment is: "You tried to use a nonvariable as a loop counter in a For...Next construction. Use a variable as the counter." But i is a variable after all, and not a Let Property function or any other expression.
What is wrong with the code?
EDIT: I should point out that the reason I want my iterator to be part of the class is that I have multiple instances of the class, serving different purposes in my project, and there are multiple nested For loops for each instance of the class. Therefore it is worth having the iterators belong to their respective objects, say:
For Client.i = 1 To Client.Count
For Order.i = 1 To Order.Count
For Item.i = 1 To Item.Count
etc.
I have settled for the following workaround but am still not entirely satisfied with it:
For ciii = 1 To Client.Count
Client.i = ciii ' Client.i is later used in the code
For oiii = 1 To Order.Count
Order.i = oiii
For iiii = 1 To Item.Count
Item.i = iiii
You cannot use MyInstance.i as the increment counter but you can use it as the terminator; e.g. For i = 1 To MyInstance.i.
MyClass class
Option Explicit
Public pi As Long
Public Property Get i() As Long
i = pi
End Property
Public Property Let i(Value As Long)
pi = Value
End Property
test sub procedure in Module1
Sub test()
Dim MyInstance As MyClass, i As Long
Set MyInstance = New MyClass
MyInstance.i = 10
For i = 1 To MyInstance.i
Debug.Print "Hello"
Next
End Sub
If you want a publicly accessible loop variable stick it at the top of a standard module i.e. declare the Public i at the top of a standard module.
Note that this would mean you need to re-write your standard module code as, as per point two, you are treating i as if it is a property/method of the class.
So, standard module code would be:
Public i As Long
Sub ........
For i = 1 To 10
Debug.Print "Hello"
Next i
End Sub ......
If you want it to somehow be a property/method then you need to define Getters and Setters (potentially) in the class. And then re-write your module code accordingly. Especially if you are planning on looping using i, you will need an incrementor method in the class.
And yes, I have changed i to Long as there are no advantages, in this case I believe, of having it declared as Integer. A Long is a safer bet for avoiding potential overflow.
If you need a workaround so that you iterate through a property of the instance, you could create a method to increment it, change your loop to a Do While ... Loop and call that method before the loop call.
'Class Module
Option Explicit
Public i As Integer
Public Sub increment_i()
i = i + 1
End Sub
Private Sub Class_Initialize()
i = 0
End Sub
'Module
Sub loop_myclass()
Dim instance As MyClass: Set instance = New MyClass
Do While instance.i <= 10
'Instance property dependent code here
Debug.Print instance.i
instance.increment_i
Loop
End Sub
OK, I found the answer. There is a Microsoft help page on For…Next loop regarding VB, but I think it pertains to VBA as well.
It says:
If the scope of counter isn't local to the procedure, a compile-time
warning occurs.
So there's not much to discuss here, it's just the way MS wants it to be. Though I'd think that if the scope is greater than the procedure it shouldn't cause any problems, but apparently it does.

How can I assign a Variant to a Variant in VBA?

(Warning: Although it might look like one at first glance, this is not a beginner-level question. If you are familiar with the phrase "Let coercion" or you have ever looked into the VBA spec, please keep on reading.)
Let's say I have an expression of type Variant, and I want to assign it to a variable. Sounds easy, right?
Dim v As Variant
v = SomeMethod() ' SomeMethod has return type Variant
Unfortunately, if SomeMethod returns an Object (i.e., a Variant with a VarType of vbObject), Let coercion kicks in and v contains the "Simple data value" of the object. In other words, if SomeMethod returns a reference to a TextBox, v will contain a string.
Obviously, the solution is to use Set:
Dim v As Variant
Set v = SomeMethod()
This, unfortunately, fails if SomeMethod does not return an object, e.g. a string, yielding a Type Mismatch error.
So far, the only solution I have found is:
Dim v As Variant
If IsObject(SomeMethod()) Then
Set v = SomeMethod()
Else
v = SomeMethod()
End If
which has the unfortunate side effect of calling SomeMethod twice.
Is there a solution which does not require calling SomeMethod twice?
In VBA, the only way to assign a Variant to a variable where you don't know if it is an object or a primitive, is by passing it as a parameter.
If you cannot refactor your code so that the v is passed as a parameter to a Sub, Function or Let Property (despite the Let this also works on objects), you could always declare v in module scope and have a dedicated Sub solely for the purpose of save-assigning that variable:
Private v As Variant
Private Sub SetV(ByVal var As Variant)
If IsObject(var) Then
Set v = var
Else
v = var
End If
End Sub
with somewhere else calling SetV SomeMethod().
Not pretty, but it's the only way without calling SomeMethod() twice or touching its inner workings.
Edit
Ok, I mulled over this and I think I found a better solution that comes closer to what you had in mind:
Public Sub LetSet(ByRef variable As Variant, ByVal value As Variant)
If IsObject(value) Then
Set variable = value
Else
variable = value
End If
End Sub
[...] I guess there just is no LetSet v = ... statement in VBA
Now there is: LetSet v, SomeMethod()
You don't have a return value that you need to Let or Set to a variable depending of its type, instead you pass the variable that should hold the return value as first parameter by reference so that the Sub can change its value.
Dim v As Variant
For Each v In Array(SomeMethod())
Exit For 'Needed for v to retain it's value
Next v
'Use v here - v is now holding a value or a reference
You could use error trapping to reduce the expected number of method calls. First try to set. If that succeeds -- no problem. Otherwise, just assign:
Public counter As Long
Function Ambiguous(b As Boolean) As Variant
counter = counter + 1
If b Then
Set Ambiguous = ActiveSheet
Else
Ambiguous = 1
End If
End Function
Sub test()
Dim v As Variant
Dim i As Long, b As Boolean
Randomize
counter = 0
For i = 1 To 100
b = Rnd() < 0.5
On Error Resume Next
Set v = Ambiguous(b)
If Err.Number > 0 Then
Err.Clear
v = Ambiguous(b)
End If
On Error GoTo 0
Next i
Debug.Print counter / 100
End Sub
When I ran the code, the first time I got 1.55, which is less than the 2.00 you would get if you repeated the experiment but with the error-handling approach replaced by the naïve if-then-else approach you discussed in your question.
Note that the more often the function returns an object, the less function calls on average. If it almost always returns an object (e.g. that is what it is supposed to return but returns a string describing an error condition in certain cases) then this way of doing things will approach 1 call per setting/ assigning the variable. On the other hand -- if it almost always returns a primitive value then you will approach 2 calls per assignment -- in which case perhaps you should refactor your code.
It appears that I wasn't the only one with this issue.
The solution was given to me here.
In short:
Public Declare Sub VariantCopy Lib "oleaut32.dll" (ByRef pvargDest As Variant, ByRef pvargSrc As Variant)
Sub Main()
Dim v as Variant
VariantCopy v, SomeMethod()
end sub
It seems this is similar to the LetSet() function described in the answer, but I figured this'd be useful anyway.
Dim v As Variant
Dim a As Variant
a = Array(SomeMethod())
If IsObject(a(0)) Then
Set v = a(0)
Else
v = a(0)
End If

Cannot use class object as argument in other class' method

I'm new to VBA and was surprised it didn't have an official dynamic array, so I tried to make a simple one that suits my needs:
Public count As Integer
Private max As Integer
Private storage(1 To 5) As DVprogram
Private Sub class_initialize()
count = 0
max = 5
End Sub
Public Sub add(data As DVprogram)
If (count + 1) > max Then
updateSize (max + 5)
End If
storage(count + 1) = data
count = count + 1
End Sub
'more code...
When I try to call add, I get the error "Object doesn't support this property or method."
Dim test As New DVprogram
Dim temp As New progDynArray
temp.add (test)
When I change the array type to Integers, everything works fine, but when I try to use one of my own classes, it always throws this error. I've tried switching between ByVal and ByRef and neither had any affect. I also found this: Passing objects to procedures in VBA, but the solution there doesn't appear to be my problem.
You need to use Set when assigning objects:
Set storage(count + 1) = data
and (as noted by Gopi) you need to drop the () when calling your add method:
temp.add test
You don't use parentheses unless you're calling a function or using the Call keyword
EDIT: parentheses (without Call) don't always cause a problem - what they do is cause the argument you're passing to be first evaluated as an expression, and the result of that evaluation then get passed. In the case of updateSize (max + 5) that's not a problem, because that's the behavior you want anyway - to add 5 to max and pass that value to updateSize. In the case of temp.add (test) you don't want test to be evaluated as an expression.
Try temp.add test
instead of temp.add(test)

.NET - Block level scope

Please have a look at the code below:
Public Class TestClass
Public TestProperty As Integer
End Class
Public Class Form1
Private Sub Form1_Load(ByVal sender As Object,
ByVal e As System.EventArgs) Handles Me.Load
Dim i As Integer
Dim j As Integer
For j = 0 To 2
For i = 0 To 10
Dim k As Integer
Dim tc As TestClass
tc = New TestClass
tc.TestProperty = tc.TestProperty + 1
k = k + 1
Next
Next
End Sub
End Class
There is a new object (called tc) created on every iteration of the FOR loop, so tc.TestProperty is always 1. Why is this not the case with variable k i.e. the value of k increments by one on every iteration? I realise this is probably to do with how value types and reference types are dealt with, but I wanted to check.
It's because when something is defined as block level it applies to the entire block level, regardless of loops. normally with control logic like an IF block statement the scope starts and ends and no code lines repeat.
Inside a loop structure the variable is defined inside that block, even though the Dim statement appears to be called multiple times it is not, it is not actually an executable statement (just a definition and reservation of a placeholder as mentioned above in one comment)
To cause it to behave in the same way as "tc" you also need to initialize it in a similar way. (the assignment to 0 would occur each loop, not the definition)
Dim k As Integer = 0
Alternately if you change how your dealing with tc it would behave the same way as k where it is in block scope the entire time inside the loop. In the below example tc is not redefined each loop either.
Dim tc as TestClass
if tc is nothing then tc = New TestClass
You would have to Dim k As Integer = 0 to keep it at 1.
This is because Dim k As Integer retains it's value, while Dim k As Integer = 0 "declares and initializes" it.
Specifically: "If you alter the value but then return to the Dim statement, your altered value is replaced by the value supplied in the Dim statement."
Actually, I don't know why it doesn't seem go out of scope. Maybe without the New keyword it's using the same block of memory.
As implied by the title of this question, you're querying the scope versus the lifetime of the variable.
The scope of the local variables k and tc is the inner For loop. The lifetime is the whole of the Sub.
If you adjusted the tc = New TestClass to If tc Is Nothing Then tc = New TestClass (and ignored the warning that causes), you should then see the tc.TestProperty increment too.
"Dim k As Integer" isn't actually translate into any code except "space reservation" (that is surely made at compile time). So the application does not pass on that sentence 10 times.
As a matter of proof, you can not put a trace bullet on that line of code !
On the other hand, your code create on each loop a fresh new object TestClass (holding a brand new variable "TestProperty) and assign it to the variable "tc". The previous object is lost and carbage collected anytime soon.

Hidden features of VBA

Locked. This question and its answers are locked because the question is off-topic but has historical significance. It is not currently accepting new answers or interactions.
Which features of the VBA language are either poorly documented, or simply not often used?
This trick only works in Access VBA, Excel and others won't allow it. But you can make a Standard Module hidden from the object browser by prefixing the Module name with an underscore. The module will then only be visible if you change the object browser to show hidden objects.
This trick works with Enums in all vb6 based version of VBA. You can create a hidden member of an Enum by encasing it's name in brackets, then prefixing it with an underscore. Example:
Public Enum MyEnum
meDefault = 0
meThing1 = 1
meThing2 = 2
meThing3 = 3
[_Min] = meDefault
[_Max] = meThing3
End Enum
Public Function IsValidOption(ByVal myOption As MyEnum) As Boolean
If myOption >= MyEnum.[_Min] Then IsValidOption myOption <= MyEnum.[_Max]
End Function
In Excel-VBA you can reference cells by enclosing them in brackets, the brackets also function as an evaluate command allowing you to evaluate formula syntax:
Public Sub Example()
[A1] = "Foo"
MsgBox [VLOOKUP(A1,A1,1,0)]
End Sub
Also you can pass around raw data without using MemCopy (RtlMoveMemory) by combining LSet with User Defined Types of the same size:
Public Sub Example()
Dim b() As Byte
b = LongToByteArray(8675309)
MsgBox b(1)
End Sub
Private Function LongToByteArray(ByVal value As Long) As Byte()
Dim tl As TypedLong
Dim bl As ByteLong
tl.value = value
LSet bl = tl
LongToByteArray = bl.value
End Function
Octal & Hex Literals are actually unsigned types, these will both output -32768:
Public Sub Example()
Debug.Print &H8000
Debug.Print &O100000
End Sub
As mentioned, passing a variable inside parenthesis causes it to be passed ByVal:
Sub PredictTheOutput()
Dim i&, j&, k&
i = 10: j = i: k = i
MySub (i)
MySub j
MySub k + 20
MsgBox Join(Array(i, j, k), vbNewLine), vbQuestion, "Did You Get It Right?"
End Sub
Public Sub MySub(ByRef foo As Long)
foo = 5
End Sub
You can assign a string directly into a byte array and vice-versa:
Public Sub Example()
Dim myString As String
Dim myBytArr() As Byte
myBytArr = "I am a string."
myString = myBytArr
MsgBox myString
End Sub
"Mid" is also an operator. Using it you overwrite specific portions of strings without VBA's notoriously slow string concatenation:
Public Sub Example1()
''// This takes about 47% of time Example2 does:
Dim myString As String
myString = "I liek pie."
Mid(myString, 5, 2) = "ke"
Mid(myString, 11, 1) = "!"
MsgBox myString
End Sub
Public Sub Example2()
Dim myString As String
myString = "I liek pie."
myString = "I li" & "ke" & " pie" & "!"
MsgBox myString
End Sub
There is an important but almost always missed feature of the Mid() statement. That is where Mid() appears on the left hand side of an assignment as opposed to the Mid() function that appears in the right hand side or in an expression.
The rule is that if the if the target string is not a string literal, and this is the only reference to the target string, and the length of segment being inserted matches the length of the segment being replaced, then the string will be treated as mutable for the operation.
What does that mean? It means that if your building up a large report or a huge list of strings into a single string value, then exploiting this will make your string processing much faster.
Here is a simple class that benefits from this. It gives your VBA the same StringBuilder capability that .Net has.
' Class: StringBuilder
Option Explicit
Private Const initialLength As Long = 32
Private totalLength As Long ' Length of the buffer
Private curLength As Long ' Length of the string value within the buffer
Private buffer As String ' The buffer
Private Sub Class_Initialize()
' We set the buffer up to it's initial size and the string value ""
totalLength = initialLength
buffer = Space(totalLength)
curLength = 0
End Sub
Public Sub Append(Text As String)
Dim incLen As Long ' The length that the value will be increased by
Dim newLen As Long ' The length of the value after being appended
incLen = Len(Text)
newLen = curLength + incLen
' Will the new value fit in the remaining free space within the current buffer
If newLen <= totalLength Then
' Buffer has room so just insert the new value
Mid(buffer, curLength + 1, incLen) = Text
Else
' Buffer does not have enough room so
' first calculate the new buffer size by doubling until its big enough
' then build the new buffer
While totalLength < newLen
totalLength = totalLength + totalLength
Wend
buffer = Left(buffer, curLength) & Text & Space(totalLength - newLen)
End If
curLength = newLen
End Sub
Public Property Get Length() As Integer
Length = curLength
End Property
Public Property Get Text() As String
Text = Left(buffer, curLength)
End Property
Public Sub Clear()
totalLength = initialLength
buffer = Space(totalLength)
curLength = 0
End Sub
And here is an example on how to use it:
Dim i As Long
Dim sb As StringBuilder
Dim result As String
Set sb = New StringBuilder
For i = 1 to 100000
sb.Append CStr( i)
Next i
result = sb.Text
VBA itself seems to be a hidden feature. Folks I know who've used Office products for years have no idea it's even a part of the suite.
I've posted this on multiple questions here, but the Object Browser is my secret weapon. If I need to ninja code something real quick, but am not familiar with the dll's, Object Browser saves my life. It makes it much easier to learn the class structures than MSDN.
The Locals Window is great for debugging as well. Put a pause in your code and it will show you all the variables, their names, and their current values and types within the current namespace.
And who could forget our good friend Immediate Window? Not only is it great for Debug.Print standard output, but you can enter in commands into it as well. Need to know what VariableX is?
?VariableX
Need to know what color that cell is?
?Application.ActiveCell.Interior.Color
In fact all those windows are great tools to be productive with VBA.
It's not a feature, but a thing I have seen wrong so many times in VBA (and VB6): Parenthesis added on method calls where it will change semantics:
Sub Foo()
Dim str As String
str = "Hello"
Bar (str)
Debug.Print str 'prints "Hello" because str is evaluated and a copy is passed
Bar str 'or Call Bar(str)
Debug.Print str 'prints "Hello World"
End Sub
Sub Bar(ByRef param As String)
param = param + " World"
End Sub
Hidden Features
Although it is "Basic", you can use OOP - classes and objects
You can make API calls
Possibly the least documented features in VBA are those you can only expose by selecting "Show Hidden Members" on the VBA Object Browser. Hidden members are those functions that are in VBA, but are unsupported. You can use them, but microsoft might eliminate them at any time. None of them has any documentation provided, but you can find some on the web. Possibly the most talked about of these hidden features provides access to pointers in VBA. For a decent writeup, check out; Not So Lightweight - Shlwapi.dll
Documented, but perhaps more obscure (in excel anyways) is using ExecuteExcel4Macro to access a hidden global namespace that belongs to the entire Excel application instance as opposed to a specific workbook.
You can implement interfaces with the Implements keyword.
Dictionaries. VBA is practically worthless without them!
Reference the Microsoft Scripting Runtime, use Scripting.Dictionary for any sufficiently complicated task, and live happily ever after.
The Scripting Runtime also gives you the FileSystemObject, which also comes highly recommended.
Start here, then dig around a bit...
http://msdn.microsoft.com/en-us/library/aa164509%28office.10%29.aspx
Typing VBA. will bring up an intellisense listing of all the built-in functions and constants.
With a little work, you can iterate over custom collections like this:
' Write some text in Word first.'
Sub test()
Dim c As New clsMyCollection
c.AddItems ActiveDocument.Characters(1), _
ActiveDocument.Characters(2), _
ActiveDocument.Characters(3), _
ActiveDocument.Characters(4)
Dim el As Range
For Each el In c
Debug.Print el.Text
Next
Set c = Nothing
End Sub
Your custom collection code (in a class called clsMyCollection):
Option Explicit
Dim m_myCollection As Collection
Public Property Get NewEnum() As IUnknown
' This property allows you to enumerate
' this collection with the For...Each syntax
' Put the following line in the exported module
' file (.cls)!'
'Attribute NewEnum.VB_UserMemId = -4
Set NewEnum = m_myCollection.[_NewEnum]
End Property
Public Sub AddItems(ParamArray items() As Variant)
Dim i As Variant
On Error Resume Next
For Each i In items
m_myCollection.Add i
Next
On Error GoTo 0
End Sub
Private Sub Class_Initialize()
Set m_myCollection = New Collection
End Sub
Save 4 whole keystrokes by typing debug.? xxx instead of debug.print xxx.
Crash it by adding: enum foo: me=0: end enum to the top of a module containing any other code.
Support for localized versions, which (at least in the previous century) supported expressions using localized values. Like Pravda for True and Fałszywy (not too sure, but at least it did have the funny L) for False in Polish... Actually the English version would be able to read macros in any language, and convert on the fly. Other localized versions would not handle that though.
FAIL.
The VBE (Visual Basic Extensibility) object model is a lesser known and/or under-utilized feature. It lets you write VBA code to manipulate VBA code, modules and projects. I once wrote an Excel project that would assemble other Excel projects from a group of module files.
The object model also works from VBScript and HTAs. I wrote an HTA at one time to help me keep track of a large number of Word, Excel and Access projects. Many of the projects would use common code modules, and it was easy for modules to "grow" in one system and then need to be migrated to other systems. My HTA would allow me to export all modules in a project, compare them to versions in a common folder and merge updated routines (using BeyondCompare), then reimport the updated modules.
The VBE object model works slightly differently between Word, Excel and Access, and unfortunately doesn't work with Outlook at all, but still provides a great capability for managing code.
IsDate("13.50") returns True but IsDate("12.25.2010") returns False
This is because IsDate could be more precisely named IsDateTime. And because the period (.) is treated as a time separator and not a date separator. See here for a full explanation.
VBA supports bitwise operators for comparing the binary digits (bits) of two values. For example, the expression 4 And 7 evaluates the bit values of 4 (0100) and 7 (0111) and returns 4 (the bit that is on in both numbers.) Similarly the expression 4 Or 8 evaluates the bit values in 4 (0100) and 8 (1000) and returns 12 (1100), i.e. the bits where either one is true.
Unfortunately, the bitwise operators have the same names at the logical comparison operators: And, Eqv, Imp, Not, Or, and Xor. This can lead to ambiguities, and even contradictory results.
As an example, open the Immediate Window (Ctrl+G) and enter:
? (2 And 4)
This returns zero, since there are no bits in common between 2 (0010) and 4 (0100).
Deftype Statements
This feature exists presumably for backwards-compatibility. Or to write hopelessly obfuscated spaghetti code. Your pick.