Actions and callables
An action names an operation. It may return a value, change application state, or call a library. Use the same action keyword for each; the result contract tells the reader which kind of operation it is.
action / returns: declare a typed operation
An order summary contains several related calculations. Keep the calculations independently callable and make the final sequence visible:
public class OrderTotals
public static action Subtotal(quantity as Integer, unitPrice as Decimal)
returns Decimal
require quantity > 0
require unitPrice >= 0
return quantity * unitPrice
end action
public static action Discount(subtotal as Decimal, rate as Decimal = 0)
returns Decimal
require subtotal >= 0
require rate >= 0 and rate <= 1
return subtotal * rate
end action
public static action Tax(net as Decimal, rate as Decimal)
returns Decimal
require net >= 0
require rate >= 0
return net * rate
end action
public static action Calculate(
quantity as Integer,
unitPrice as Decimal,
taxRate as Decimal,
discountRate as Decimal = 0
)
returns Decimal
let subtotal = Subtotal(quantity, unitPrice)
let discount = Discount(subtotal, discountRate)
let net = subtotal - discount
let tax = Tax(net, taxRate)
return net + tax
end action
public static action Run()
let total = Calculate(3, 25, taxRate: 0.18, discountRate: 0.10)
Global.System.Console.WriteLine("Order total: {{total}}")
end action
end class
An optional parameter has a default value. Required parameters precede optional ones. Named arguments make this call's two rates distinct without forcing the reader to remember their positions.
The example deliberately leaves rounding to a separate financial policy. Decimal arithmetic and a variable named tax do not decide where a business must round, what legal tax rates apply, or which currency an amount represents.
return / result: complete an action
returns Decimal states the logical result type. return expression produces that value and exits. A value-returning action must produce a result on every normal path unless it explicitly declares an initial result.
The contextual result can be assigned without immediately exiting:
public class DiscountPolicy
public static action Rate(isPreferred as Boolean, orderCount as Integer)
returns Decimal = 0
require orderCount >= 0
ensure result >= 0 and result <= 1
if isPreferred then
result = 0.10
else
if orderCount >= 20 then
result = 0.05
end if
end if
end action
end class
The declared zero default permits successful fallthrough. Without that default or a proven result assignment, an incomplete path is a compile-time error. An absent class result is never silently substituted to make a missing return legal.
Choose the form that exposes the policy clearly. Direct return statements suit independent branches; result suits a calculation assembled before a common successful exit. take belongs to constructs that explicitly produce scoped values and does not replace return in an action.
self / arguments: distinguish members from inputs
A parameter and a member with the same name cannot be distinguished by writing one with a capital letter. Qualify the intended symbol:
public class CustomerName
public Name as String = ""
public action Rename(name as String)
require arguments.name is not nothing
require arguments.name is not whitespace
self.Name = arguments.name.Trim()
end action
end class
self is the current object. arguments.name names the current action parameter. This is statically bound parameter access, not a reflection dictionary. It also avoids turning a casing convention into a correctness requirement.
Ordinary value parameters receive their argument value. Reassigning a value parameter does not rebind the caller's local. Reference parameters have a separate explicit contract; use them when caller-visible rebinding is actually required.
lambdas / selectors: pass behavior to a library
Many .NET APIs accept a delegate or an expression tree. DirectSharp provides receiver-based and named-parameter expressions for these contexts:
import Global.System.Linq.*
public class Customer
public Name as String = ""
public IsActive as Boolean = false
public CreditLimit as Decimal = 0
end class
public class CustomerQueries
public static action Eligible(
customers as Global.System.Collections.Generic.IEnumerable <of Customer>
)
returns Global.System.Collections.Generic.List <of Customer>
require customers is not nothing
return customers
.Where(.IsActive)
.Where(customer: customer.CreditLimit > 0)
.OrderBy(.Name)
.ToList()
end action
end class
.IsActive is an expression over the lambda's receiver. The named form makes the subject explicit when the expression needs it. These are ordinary calls to LINQ; Where, OrderBy and ToList are library methods.
The target parameter determines whether the compiler supplies a delegate or an expression tree. An IQueryable provider keeps its translation restrictions. A named action containing arbitrary statements does not automatically become a database expression tree.
LINQ pipelines commonly defer execution. ToList materializes this pipeline, so the caller receives the results computed during this call. Omitting it and returning a sequence would make enumeration time part of the API's behavior.
of / where: declare generic actions
public class SequenceTools
public static action FirstOrDefault <of T>(
items as Global.System.Collections.Generic.IEnumerable <of T>
)
returns T
require items is not nothing
for_each item in items
return item
end for_each
return default
end action
end class
The generic type connects the sequence's element type to the result. This application helper returns the type's effective default when the sequence is empty. For a reference type whose effective default is absence, that is nothing; for another type, it can be a real value. If callers must distinguish emptiness from a valid default-valued item, use an explicit result model instead of this convenience action.
See action contracts for binding and callable details.
Practice: actions and callable values
Repair a parameter/property rename without relying on capitalization. The answer separates name binding from validation policy.
Previous: Conditions and loops · Next: Objects and class kinds