Why declare function's type? - excel

Sorry if this has been asked, but seriously can't find anything, so would also appreciate on how to search for this stuff.
So my question: what is the point of declaring the function's type in general? E.g. here 'as double'
Function myFunction(ByVal j As Integer) As Double
Return 3.87 * j
End Function
For a normal variable it has tons of benefits, like less memory, easier to see typos, but why here?
Edit: so, it's good because we can avoid errors, like it giving back a different type of values than expected.

Functions RETURN something. That type is the type of the return.
In your function:
Function myFunction(ByVal j As Integer) As Double
Return 3.87 * j
End Function
You are returning a decimal, so type Double make sense.
If you don't return anything, then you can declare it as a Sub.
And, for clarification, your function would throw a compile error. Unlike other languages, in VBA to return, we set the function name's value to the thing we want to return:
Function myFunction(ByVal j As Integer) As Double
myFunction=3.87 * j
End Function
Now we can call this function to get the Double value that it creates:
Sub testSub()
msgbox("This is the result of the function: " & myFunction(10))
End Sub
Which would launch a message box saying "This is the result of the function: 38.7"

Since I can't mark a comment the answer, let me quote:
#John Coleman
My opinion is that it a good thing to declare your return types because it increases the likelihood that the compiler will complain when you are doing something that really doesn't make sense.

Excel VBA is different from other programming languages in that it centers around a particular application: Excel.
Functions are useful in Excel VBA primarily because they can be typed directly into a cell on a sheet by an end user. User defined functions provide near infinite flexibility. The value the user defined function prints to Excel is formatted based on the function's type--and in a program which is about data visualization, formatting is a huge part.
For example, try putting these four functions into a blank worksheet module:
Function myInt(x, y) As Integer
myInt = x / y
End Function
Function myDouble(x, y) As Double
myDouble = x / y
End Function
Function myString(x, y) As String
myString = x / y
End Function
Function myVariant(x, y)
myVariant = x / y
End Function
Next, enter each of these functions into a different cell in the workbook. Use x=1 and y=2.
myInt produces "=0"
myDouble produces "=0.5"
myString produces "'0"
myVariant produces "=0.5"
If you're okay with Excel deciding how to format your result, that's your choice, but specifying the type offers an entire new level of control. For example, by simply declaring a function an integer, you can avoid having to devote a line of code to rounding. By declaring a function to be a string, you can avoid several lines of formatting code trying to get a number to be saved as text instead.

Related

Using Subroutine for output instead of a function

I thought at this point that the role of functions and subroutines was very clear to me. But now I am not so sure... I see it written all the time
"Functions can return values / subroutines cannot return a value."
and
"a function can only return a single value" (I realize they can return arrays and such too).
But it seems as though I can effectively "return a value from a subroutine" if I pass the "result" variable into the subroutine... Is this considered a "poor practice?" or am I missing some other key concept here...
Method # 1 (Using a Function):
Sub test1()
Dim x As Integer
Dim y As Integer
Dim z As Integer
x = 2
y = 3
z = test2(x, y)
End Sub
Function test2(var1 As Integer, var2 As Integer) As Integer
test2 = var1 + var2
End Function
Method # 2 (Using a Subroutine):
Sub test3()
Dim x As Integer
Dim y As Integer
Dim z As Integer
Call test4(x, y, z)
End Sub
Sub test4(var1 As Integer, var2 As Integer, var3 As Integer)
var1 = 2
var2 = 3
var3 = var1 + var2
End Sub
Usually, it is bad practice to change the value of a parameter. Just look at you examples - it is obvious that your function does something with the 2 parameters and returns a value (which you write to z). In the second example, you don't see what will happen unless you look to the subroutine - and not only to the function definition, you need to read the complete code so that you can tell what parameter will manipulated and what not.
In software development, when you call a subroutine, you don't want to look at this subroutine - and often it is even not available for you. Let the subroutine do it's job, but without any side effects.
Use a function whenever possible, it keeps your code much more readable. Trust me...
There are (very few) cases when you want to receive more than one result from a subroutine. In that case, I would advice to put explicitly the keyword ByRef in front of the parameter (even if in VBA this is technically not necessary because it's the default). And put a comment that tells why it is the case. You will thank yourself when you look at your code weeks, months or years later.

Calculate (If A=B, then "" else A) in Excel without evaluating A twice

I'm intending to conduct a formula of the type:
=IF(VOL("Site";"Date")=0;"";VOL("Site";"Date"))
where VOL is a function I'm using through an Add-In. The limitations of this Add-In is, among others, that it is prohibited to call two Add-In function inside a single formula. I.e. the code I've written above is invalid and will result in an error.
Is there a way of achieving the following:
=IF(LHS=RHS;"Value if True";LHS) (2)
where LHS is Left hand side, RHS right hand side and the expression therefore checks if LHS is equal to RHS, and if so prints a corresponding value, else LHS, without having Excel evaluate LHS twice?
I haven't found any solution to this except importing the formula in one cell, and refer to that cell as the value to print if the logical expression in the IF statement is false, but this will become a quite extensive "double work". A solution like (2) would also become more readable, especially when LHS is of the type "'C:\pathtofile[filename]SheetName!'Cell".
Hope anyone has some clever solution to this
Here is one (rather ugly) way, just using formulas:
=IFERROR(1/IFERROR(1/vol("Site";"Date"),0),"")
This makes use of the IFERROR function, which kind of does what you want but only tests for errors. Division by zero results in an error, so the inner IFERROR returns zero if VOL is zero, and 1/VOL otherwise. Now we need to take the reciprocal again to return the original value, so we repeat the trick, this time returning "" if there is an error.
If you want to test for another value (e.g. 3), just use something like:
=IFERROR(3+1/IFERROR(1/(vol("Site";"Date")-3),0),"")
A much neater way would be to create a function in VBA which wraps the VOL function and does what you want:
Public Function MyVol(varSite As Variant, varDate As Variant) As Variant
MyVol = vol(varSite, varDate)
If MyVol = 0 Then MyVol = ""
End Function
Assuming you can call VOL from VBA.

Excel VBA failure of repeated Evaluate method

I have written a little tool in VBA that charts a function you pass it as a string (e.g. "1/(1+x)" or "exp(-x^2)"). I use the built-in Evaluate method to parse the formula. The nub of it is this function, which evaluates a function of some variable at a given value:
Function eval(func As String, variable As String, value As Double) As Double
eval = Evaluate(Replace(func, variable, value))
End Function
This works fine, e.g. eval("x^2, "x", 2) = 4. I apply it element-wise down an array of x values to generate the graph of the function.
Now I want to enable my tool to chart the definite integral of a function. I have created an integrate function which takes an input formula string and uses Evaluate to evaluate it at various points and approximate the integral. My actual integrate function uses the trapezoidal rule, but for simplicity's sake let's suppose it is this:
Function integrate(func As String, variable As String, value As Double) As Double
integrate = value * (eval(func, variable, 0) + eval(func, variable, value)) / 2
End Function
This also works as expected, e.g. integrate("t", "t", 2) = 2 for the area of the triangle under the identity function.
The problem arises when I try to run integrate through the charting routine. When VBA encounters a line like this
eval("integrate(""t"",""t"",x)", "x", 2)
then it will stop with no error warning when Evaluate is called inside the eval function. (The internal quotes have to be doubled up to read the formula properly.) I expect to get the value 2 since Evaluate appears to try and evaluate integrate("t", "t", 2)
I suspect the problem is with second call on Evaluate inside integrate, but I've been going round in circles trying to figure it out. I know Evaluate is finicky and poorly documented http://fastexcel.wordpress.com/2011/11/02/evaluate-functions-and-formulas-fun-how-to-make-excels-evaluate-method-twice-as-fast but can anyone think of a way round this?
Thanks
George
Excel 2010 V14, VBA 7.0
Thanks Chris, your Debug.Print suggestion got me thinking and I narrowed the problem down a bit more. It does seem like Evaluate gets called twice, as this example shows:
Function g() As Variant
Debug.Print "g"
g = 1
End Function
Run from the Immediate Window:
?Evaluate("g()")
g
g
1
I found this http://www.decisionmodels.com/calcsecretsh.htm which shows a way round this by using Worksheet.Evaluate (Evaluate is actually the default for Application.Evaluate):
?ActiveSheet.Evaluate("g()+0")
g
1
However this still doesn't solve the problem with Evaluate calling itself. Define
Function f() As Variant
Debug.Print "f"
f = ActiveSheet.Evaluate("g()+0")
End Function
Then in the Immediate Window:
?ActiveSheet.Evaluate("f()+0")
f
Error 2015
The solution I found was define a different function for the second formula evaluation:
Function eval2(formula As String) As Variant
[A1] = "=" & formula
eval2 = [A1]
End Function
This still uses Excel's internal evaluation mechanism, but via a worksheet cell calculation. Then I get what I want:
?eval2("f()")
f
g
1
It's slower due to the repeated worksheet hits, but that's the best I can do. So in my original example, I use eval to calculate the integral and eval2 to chart it. Still interested if anyone has any other suggestions.

Adding two numbers with a function

I am writing VBA code to add two numbers:
Option Explicit
Dim x As Integer, y As Integer
Function addtwo(x, y)
addtwo = x + y
End Function
This is not a good function since I have to manually input the values x and y to get for the result. How can I modify the codes so it can work with any two numbers, no matter integers or numbers with decimals?
You define your input variables in the FUNCTION declaration line, not above it. I would recommend the vartype DOUBLE for whole numbers and decimals.
Function ADDTWO(ByVal x As Double,ByVal y As Double) As Double
ADDTWO = x + y
End Function
Now this function can be used in a cell as:
=ADDTWO(3, 6)
or
=ADDTWO(A2, B7)
It can also be used in VBA.
Of course, the SUM() function does this same thing, so....I presume this is a learning exercise.
As far as working with any two numbers, try the Variant data type instead of Integer. Not sure what you mean by manually inputting the values - are you talking about getting them from the worksheet?

What is the great wisdom in defining your own object if I can't do this?

This is for Excel and VBA. Assume that BondClass has been properly defined in a Class Module. I get a #VALUE! when I type "=GetBondPrincipal()" into an Excel cell. Have I done something syntactically wrong or is this just not possible in Excel/VBA? I ask because what I really want to do is this:
Return a User Defined Data Type in an Excel Cell
but am unable to find a solution. So at the very least, I want to know if what I want to do below is possible.
Option Explicit
Function InitializeBond(ir As Double, p As Double) As BondClass
Dim mybond As BondClass
Set mybond = New BondClass
Call mybond.Initialize(ir, p)
InitializeBond = mybond
End Function
Function GetBondPrincipal()
Dim b As BondClass
Set b = New BondClass
b = InitializeBond(0.03, 100) //the code quits here,
//it doesn't like the BondClass return type?
GetBondPrincipal = b.GetPrincipal()
End Function
I know that in the example I have provided, I don't have to call InitializeBond and can simply type "Call b.Initialize(.03,100)". The code will work fine if I do this. But I can't seem to be able to get a UDF to return a type other than the built-in types. Any way to do any of this? Do I have to define assignment for non-built-in types?
Hint: use "SET".
(most common error made by VB programmers in VB6 and VBA).
Anyway, I don't find that very elegant.
I would rather write:
Function GetBondPrincipal()
Dim b As BondClass
Set b = New BondClass
with b
.param1 = 0.03
.param2 = 100
.InitializeBond
GetBondPrincipal = .GetPrincipal()
end with
End Function

Resources