
Working with files in Dyalog APL
Hello. Today I will tell (and show) you how file management is organized in Dyalog APL. We will experiment with the “native” Dyalog file system - DCF, the rest of the files are not considered in this article. The topic of exception handling will also be covered. All experiments will be conducted on material from the past topic. Unfortunately, some images are worthless, please excuse me, because I don’t know a good image hosting service. So let's go.
Dyalog APL has its own way of storing information - component files. The bottom line is this: a
file is a sequence of "cells" (component), and in each of them you can write the value of only one variable. The following operations can be performed on a file: create, open, read a component, replace a component, attach a new component to the end, close various special operations that are not considered in this article.
Let's try to open some file:

And we get an error, because there is no file. From this “experiment”, 2 conclusions can be drawn:
- the system function [] FTIE is used to open the file. Its arguments are the full file name on the left and a special file identifier (tie number), which is subsequently used to access the file;
- error messages are displayed in the interpreter window indicating the cause of the error (in our case, FILE NAME ERROR). Looking ahead a bit, I note that each error has its own number, which is used to intercept these same errors.
So, the file could not be opened. So you need to create it! To do this, use the [] FCREATE system function with the same arguments as [] FTIE.

Well, now we have the test.dcf file with identification number 1. By default (if you do not specify the full path), the files appear on the desktop. The created files do not contain components, which can be verified using the system function [] FSIZE, passing it the file number on the right.

So far, we are only interested in the second element of the result of this function — it contains the number of the next free component. Since this number is 1 for the test.dcf file, the number of components is zero.
To fill in the files, the system function [] FAPPEND is used, to which you need to pass a variable or value on the left and the file number on the right. To verify that the information is actually recorded, use the [] FREAD function. It takes one argument out of two values - file number and component number.

To close the file, use the [] FUNTIE function with the file number. To make sure that there is no more connection with the file, we use the nyladic function [] FNUMS, which returns the vector of file numbers.

Using this knowledge, we will try to modify the program code from the previous example.
We will make several changes to the program:
1. We will write the text used in the program to the file and we will load it at execution.
2. We will write the texts of the functions to another file and will execute them when the workspace is loaded, and upon exit, save them back to the file.
First, we write a function that will initialize the program text - txtIni. We need: open the file, if the file does not exist, create it and write the text, read the text into a global (for this function) variable and close the file.
The title of the function will look like this:

The function takes the fully qualified file name as an argument (fname). The tien variable will store the file number.
When working with files, it is easy to make a mistake, so you need to use error handling tools. For our example, the construction: Trap: Else: EndTrap, which is somewhat similar in ideology to try-catch, is suitable.

Design: Trap 0 1000 defines the error codes to be intercepted. In this case, these are all system events (0) and all user events (1000). Also, within these boundaries, you can define the processing of individual events using the constructs: Case and: CaseList. Note the use of the L1 label: - it is used to jump unconditionally after handling the exception. The rest is simple: if the file contains components, read the first into the txt variable, if not, fill it with text and write to the file. Then we close the file.
Next, you need to determine the sequence of actions in case of errors. For the absence of the file, we write a separate: Case, for the rest -: Else. The simplest thing that can be done in case of unexpected errors is to close all open files with the [] FUNTIE function with the [] FNUMS argument, and then display a message in the interpreter window using the [] SIGNAL function. Its arguments are the message text on the right and the error code on the left. In our example, it will be enough to use the system variables [] DM (text) and [] EN (error code). If there is no file (or an incorrectly specified name), this file is created and then the transition to the label is performed. Everything is very simple!

In order for the loaded text to be used in the program, you need to slightly change the application functions by adding calls to the elements of the text nested vector txt.

The argument was removed in the WhatNum function, since its value (form heading) is now taken from the file. All symbolic constants were replaced with a call to the elements of the txt vector with a call to the Disclose function, which converts the nested vector element to a symbol vector. In the checkNum function, the changes are the same:

And now the most interesting: in component files can variables be stored, but can functions be stored? YES! To do this, you only need to convert the function to text and assign it to a variable, which the saveFns function will do.
The conversion is done instantly: the system function [] CR (canonical representation) with the name of the function as an argument returns a character matrix with text. But the real magic begins with the use of the "Each" operator.

Thus, the variable all contains three texts of our functions, and each text is a symbol matrix represented as a scalar (rank is 0)!
Then follows the already described procedure for writing to a file. At the end we add a call to the system function [] EX, which will remove unnecessary objects from the workspace.

Now it remains to write the initial initialization code, which will be executed when the workspace is loaded. The sequence of actions is this: opening a file, reading, closing a file. Then the magic of APL starts working again: with a slight movement of the hand, the vector all turns into full-fledged functions of the workspace! After loading the functions, you can call WhatNum and make sure that everything works.

Excellent. It remains only to save our functions to a file, change the contents of the system variable [] LX, and save the work area. After that, only functions for working with files will be located in the workspace, and the application code will be loaded.

To start the program, just open the workspace file, and if everything is done correctly, the application window will appear on the screen.
Today you saw a bit of APL magic, and at the same time you learned about such important things as working with files and handling exceptions. To be continued.
What is DCF?
Dyalog APL has its own way of storing information - component files. The bottom line is this: a
file is a sequence of "cells" (component), and in each of them you can write the value of only one variable. The following operations can be performed on a file: create, open, read a component, replace a component, attach a new component to the end, close various special operations that are not considered in this article.
Let's try to open some file:

And we get an error, because there is no file. From this “experiment”, 2 conclusions can be drawn:
- the system function [] FTIE is used to open the file. Its arguments are the full file name on the left and a special file identifier (tie number), which is subsequently used to access the file;
- error messages are displayed in the interpreter window indicating the cause of the error (in our case, FILE NAME ERROR). Looking ahead a bit, I note that each error has its own number, which is used to intercept these same errors.
So, the file could not be opened. So you need to create it! To do this, use the [] FCREATE system function with the same arguments as [] FTIE.

Well, now we have the test.dcf file with identification number 1. By default (if you do not specify the full path), the files appear on the desktop. The created files do not contain components, which can be verified using the system function [] FSIZE, passing it the file number on the right.

So far, we are only interested in the second element of the result of this function — it contains the number of the next free component. Since this number is 1 for the test.dcf file, the number of components is zero.
To fill in the files, the system function [] FAPPEND is used, to which you need to pass a variable or value on the left and the file number on the right. To verify that the information is actually recorded, use the [] FREAD function. It takes one argument out of two values - file number and component number.

To close the file, use the [] FUNTIE function with the file number. To make sure that there is no more connection with the file, we use the nyladic function [] FNUMS, which returns the vector of file numbers.

Using this knowledge, we will try to modify the program code from the previous example.
A bit of practice
We will make several changes to the program:
1. We will write the text used in the program to the file and we will load it at execution.
2. We will write the texts of the functions to another file and will execute them when the workspace is loaded, and upon exit, save them back to the file.
Saving and loading text
First, we write a function that will initialize the program text - txtIni. We need: open the file, if the file does not exist, create it and write the text, read the text into a global (for this function) variable and close the file.
The title of the function will look like this:

The function takes the fully qualified file name as an argument (fname). The tien variable will store the file number.
When working with files, it is easy to make a mistake, so you need to use error handling tools. For our example, the construction: Trap: Else: EndTrap, which is somewhat similar in ideology to try-catch, is suitable.

Design: Trap 0 1000 defines the error codes to be intercepted. In this case, these are all system events (0) and all user events (1000). Also, within these boundaries, you can define the processing of individual events using the constructs: Case and: CaseList. Note the use of the L1 label: - it is used to jump unconditionally after handling the exception. The rest is simple: if the file contains components, read the first into the txt variable, if not, fill it with text and write to the file. Then we close the file.
Next, you need to determine the sequence of actions in case of errors. For the absence of the file, we write a separate: Case, for the rest -: Else. The simplest thing that can be done in case of unexpected errors is to close all open files with the [] FUNTIE function with the [] FNUMS argument, and then display a message in the interpreter window using the [] SIGNAL function. Its arguments are the message text on the right and the error code on the left. In our example, it will be enough to use the system variables [] DM (text) and [] EN (error code). If there is no file (or an incorrectly specified name), this file is created and then the transition to the label is performed. Everything is very simple!

In order for the loaded text to be used in the program, you need to slightly change the application functions by adding calls to the elements of the text nested vector txt.

The argument was removed in the WhatNum function, since its value (form heading) is now taken from the file. All symbolic constants were replaced with a call to the elements of the txt vector with a call to the Disclose function, which converts the nested vector element to a symbol vector. In the checkNum function, the changes are the same:

Hiding code
And now the most interesting: in component files can variables be stored, but can functions be stored? YES! To do this, you only need to convert the function to text and assign it to a variable, which the saveFns function will do.
The conversion is done instantly: the system function [] CR (canonical representation) with the name of the function as an argument returns a character matrix with text. But the real magic begins with the use of the "Each" operator.

Thus, the variable all contains three texts of our functions, and each text is a symbol matrix represented as a scalar (rank is 0)!
Then follows the already described procedure for writing to a file. At the end we add a call to the system function [] EX, which will remove unnecessary objects from the workspace.

Now it remains to write the initial initialization code, which will be executed when the workspace is loaded. The sequence of actions is this: opening a file, reading, closing a file. Then the magic of APL starts working again: with a slight movement of the hand, the vector all turns into full-fledged functions of the workspace! After loading the functions, you can call WhatNum and make sure that everything works.

Excellent. It remains only to save our functions to a file, change the contents of the system variable [] LX, and save the work area. After that, only functions for working with files will be located in the workspace, and the application code will be loaded.

To start the program, just open the workspace file, and if everything is done correctly, the application window will appear on the screen.
Total
Today you saw a bit of APL magic, and at the same time you learned about such important things as working with files and handling exceptions. To be continued.