Rocket Uniface User Forum

 View Only

An In-Depth guide to 'Functions': Everything you need to know.

By Jan Cees Boogaard posted 02-15-2023 03:00

  

An In-Depth guide to 'Functions': Everything you need to know.

Created by Frank Doodeman, last modified on Feb 13, 2023

We are excited to share that we've enhanced the Uniface ProcScript language with a brand new ProcScript module type called function, which will be introduced in Uniface patch version 10.4.02.016. This new module type will help you write better isolated, self-contained, and therefore reusable code.

So, why are functions needed?

The Uniface ProcScript language has always had three types of modules:

  • Triggers - modules that are executed in response to an event on a specific type of object including component, collection, occurrence, fields, or startup shell. They are declared using the trigger and webtrigger  keywords.
  • Operations - modules that are part of a procedural API of a specific type of object including component, collection, or occurrence. They are executed in response to an explicit procedural invocation like activate or an inline reference to the operation name and its object handle. Operations are declared using the operation and weboperation  keywords.
  • Entries - modules that are executed in response to an explicit procedural invocation like call or an inline reference to the entry name. They are declared using the entry keyword.

Operations and triggers have a namespace based on the object they are declared on. Unlike operations and triggers, entries have a namespace that is fixed to the component, regardless of where the entry is defined.

In any programming language, it is preferable to write code in clear and maintainable functional units. Therefore, instead of having a large trigger module or an operation module, a programmer would factor out the functional parts into dedicated sub-modules. The obvious type of module to use for dedicated sub-modules is the entry type module. Unfortunately, entry modules have a drawback; they are treated like they are defined at the component level, even if they are declared at the entity or field level. In case multiple entries exist with the same name, whether knowingly or unknowingly, it becomes difficult to predict and understand which entry will be compiled into the component (even though it will always be ONLY ONE!). Having two entries defined for multiple entities or fields with the same name but with different implementations is impossible.

The Uniface documentation even suggests that you declare all entry modules in the Script container of the component, to create a clear overview of the component's entries. This at least makes your entry administration understandable, but it does not solve the problem of having two entries with the same name but with different implementations. The alternative is to use partner operations instead. That does makes sense, but operations feel more heavyweight than entries; operations are the public/partner interface of an object whereas entries always feel like the private helper functions.

So, entries have some practical limitations: 

  • If you want to factor out specific parts of a trigger or an operation module, ideally, you'd want the resulting entry modules to be close to that trigger or operation. In many cases, the trigger or operation is not at the level of the component.
  • If you want to factor out parts of a trigger or an operation module that is defined in a modeled entity, there is simply no component Script container available.
  • If you choose to use a partner operation module instead of an entry module, you would be adding the definition of the operation to the object's API, which is not always desirable.

So, again, even if you define your entry modules near the trigger or operation module that uses them, each such entry can be accessed by code anywhere in the component. You need to be aware of this when you factor out functional parts of your triggers or operations into entries.  To avoid accidentally using an entry module name that you – or a colleague – has already used when factoring out functional parts in a different trigger or operation, or even for some different entity or field, you need to have a naming scheme in place.

To addresses these issues, we've introduced the new function ProcScript module type. Pleaes note, you can still use the entry Procscript module type in the same way you are used to. However, in many cases it is preferable to use functions instead. Read on for more information on that!

What exactly is a function module?

Function modules are declared using the function keyword and, like entries, can be invoked using the call statement or inline by referencing its name (followed by a pair of matching brackets).

Below is a simple example of how to define a function and how to call the function:

Functions
function doSomething
throws
returns string               ; set the data type of the return value
variables
  string text
endvariables
  text = "5.5 is returned"
  return text                ; return the value
end
 
; invoked as an inline function
vResult = doSomething()      ; vResult = "5.5 is returned"
 
; invoked as a subroutine
call doSomething()           ; $status = 5 ($status is limited to integers)

Function modules are like entry modules, with some differences:

  • They are declared using the function keyword.
  • They have the namespace of the object they are declared on, so multiple objects in a component can have a function with the same name.
  • They are visible only to the object itself and its child object in the component's name-structure.

So, a function module can be declared at several levels:

  • A function declared in the Script container of a field can only be invoked by other code of that field. Code of other fields or code of the entity or component cannot invoke the function.
  • A function declared in one of the Script containers of an entity can only be invoked by other code of that entity and by code of its fields. Code of other (child) entities and their fields, or the component, cannot invoke the function.
  • A function declared in the Script container of a component is functionally equivalent to an entry type module; it can be invoked by code in the component and in all entities and fields that it contains.

For more information on how the compiler deals with entries and functions that have the same name, please read the Uniface documentation.

How do functions help you?

Using a function module, you can easily factor out functional parts of your field-level triggers, entity-level triggers and entity-level operations. You'll no longer have to worry about name clashes with functions of other fields and entities. With the new function module, it will be easier to write self-contained entity and field code, improving its maintainability. The function module also allows you to keep your factored-out private helper functions close to the code of your triggers and the operations that need them, without making them available to code in other parts of the application.

If you would like some of your entries to have a limited visibility like functions have, then you only need to replace the keyword entry with the keyword function and voilà, you're done!

On top of that, since functions at the component-level are equivalent to entries, you can simply use functions without the need to consider entries; functions will be your new best friend for writing private helper modules! Isn't that great!!!


#tofp

0 comments
34 views

Permalink