- •Foreword
- •1. Introduction
- •2. Culture Shock
- •3. Preliminaries
- •Notation Used in This Book
- •Terminology
- •Sentences (statements)
- •Word Formation (tokenizing rules)
- •Numbers
- •Characters
- •Valence of Verbs (Binary and Unary Operators)
- •How Names (Identifiers) Get Assigned
- •Order of Evaluation
- •How Names Are Substituted
- •What a verb (function) looks like
- •Running a J program
- •The Execution Window; Script Windows
- •Names Defined at Startup
- •Step-By-Step Learning: Labs
- •J Documentation
- •Getting Help
- •4. A First Look At J Programs
- •Average Daily Balance
- •Calculating Chebyshev Coefficients
- •5. Declarations
- •Arrays
- •Cells
- •Phrases To Memorize
- •Constant Lists
- •Array-creating Verbs
- •6. Loopless Code I—Verbs Have Rank
- •Examples of Implicit Loops
- •The Concept of Verb Rank
- •Verb Execution—How Rank Is Used (Monads)
- •Controlling Verb Execution By Specifying a Rank
- •Examples Of Verb Rank
- •Negative Verb Rank
- •Verb Execution—How Rank Is Used (Dyads)
- •When Dyad Frames Differ: Operand Agreement
- •Order of Execution in Implied Loops
- •A Mistake To Avoid
- •7. Starting To Write In J
- •8. More Verbs
- •Arithmetic Dyads
- •Boolean Dyads
- •Min and Max Dyads
- •Arithmetic Monads
- •Boolean Monad
- •Operations on Arrays
- •9. Loopless Code II—Adverbs / and ~
- •Modifiers
- •The Adverb Monad u/
- •The adverb ~
- •10. Continuing to Write in J
- •11. Boxing (structures)
- •Terminology
- •Boxing As an Equivalent For Structures In C
- •12. Compound Verbs
- •Verb Sequences—u@:v and u@v
- •Making a Monad Into a Dyad: The Verbs [ and ]
- •Making a Dyad Into a Monad: u&n and m&v
- •13. Empty Operands
- •Execution On a Cell Of Fills
- •Empty cells
- •If Fill-Cells Are Not Enough
- •14. Loopless Code III—Adverbs \ and \.
- •15. Verbs for Arithmetic
- •Dyads
- •Monads (all rank 0)
- •16. Loopless Code IV
- •A Few J Tricks
- •Power/If/DoWhile Conjunction u^:n and u^:v
- •Tie and Agenda (switch)
- •17. More Verbs For Boxes
- •Dyad ; (Link) And Monad ; (Raze)
- •Dyad { Revisited: the Full Story
- •Split String Into J Words: Monad ;:
- •Fetch From Structure: Dyad {::
- •Report Boxing Level: Monad L.
- •18. Verb-Definition Revisited
- •What really happens during m :n and verb define
- •Compound Verbs Can Be Assigned
- •Dual-Valence verbs: u :v
- •The Suicide Verb [:
- •Multi-Line Comments Using 0 :0
- •Final Reminder
- •The Obverse u^:_1
- •Apply Under Transformation: u&.v and u&.:v
- •Defined obverses: u :.v
- •An observation about dyadic verbs
- •20. Performance: Measurement & Tips
- •Timing Individual Sentences
- •Compounds Recognized by the Interpreter
- •Use Large Verb-Ranks! and Integrated Rank Support
- •Shining a Light: The J Performance Monitor
- •21. Input And Output
- •Foreigns
- •File Operations 1!:n; Error Handling
- •Treating a File as a Noun: Mapped Files
- •Format Data For Printing: Monad And Dyad ":
- •Format an Array: 8!:n
- •Format binary data: 3!:n
- •printf, sprintf, and qprintf
- •Convert Character To Numeric: Dyad ".
- •22. Calling a DLL Under Windows
- •Memory Management
- •Aliasing of Variables
- •23. Socket Programming
- •Asynchronous Sockets and socket_handler
- •Names and IP Addresses
- •Connecting
- •Listening
- •Other Socket Verbs
- •24. Loopless Code V—Partitions
- •Find Unique Items: Monad ~. and Monad ~:
- •Apply On Subsets: Dyad u/.
- •Apply On Partitions: Monad u;.1 and u;.2
- •Apply On Specified Partitions: Dyad u;.1 and u;.2
- •Apply On Subarray: Dyad u;.0
- •Apply On All Subarrays: Dyad u;.3 and u;._3
- •Extracting Variable-Length Fields Using ^: and ;.1
- •Example: Combining Adjacent Boxes
- •25. When Programs Are Data
- •Calling a Published Name
- •Using the Argument To a Modifier
- •Invoking a Gerund: m`:6
- •Passing the Definition Of a Verb: 128!:2 (Apply)
- •Passing an Executable Sentence: Monad ". and 5!:5
- •26. Loopless Code VI
- •28. Modifying an array: m}
- •Monad I.—Indexes of the 1s in a Boolean Vector
- •29. Control Structures
- •while./do./end. and whilst./do./end.
- •if./do./else./end., if./do./elseif./do./end.
- •try./catch./catcht./end. and throw.
- •return.
- •assert.
- •30. Modular Code
- •Locales And Locatives
- •Assignment
- •Name Lookup
- •Changing The Current Locale
- •The Shared Locale 'z'
- •Using Locales
- •31. Writing Your Own Modifiers
- •Modifiers That Do Not Refer To x. Or y.
- •Modifiers That Refer To x. Or y.
- •32. Applied Mathematics in J
- •Complex Numbers
- •Matrix Operations
- •Calculus: d., D., D:, and p..
- •Taylor Series: t., t:, and T.
- •Hypergeometric Function with H.
- •Sparse Arrays: Monad and Dyad $.
- •Random Numbers: ?
- •Computational Addons
- •Useful Scripts Supplied With J
- •33. Elementary Mathematics in J
- •Verbs for Mathematics
- •Extended Integers, Rational Numbers, and x:
- •Factors and Primes: Monad p:, Monad and Dyad q:
- •Permutations: A. and C.
- •34. Graphics
- •Plot Package
- •2D Graphics: the gl2 Library
- •Displaying Tabular Data: the Grid Control
- •3D Graphics: OpenGL
- •35. Odds And Ends
- •Dyad # Revisited
- •Boxed words to string: Monad ;:^:_1
- •Spread: #^:_1
- •Choose From Lists Item-By-Item: monad m}
- •Recursion: $:
- •Make a Table: Adverb dyad u/
- •Cartesian Product: Monad {
- •Boolean Functions: Dyad m b.
- •Operations Inside Boxes: u L: n, u S: n
- •Comparison Tolerance !.f
- •Right Shift: Monad |.!.f
- •Generalized Transpose: Dyad |:
- •Monad i: and Dyad i:
- •Fast String Searching: s: (Symbols)
- •Fast Searching: m&i.
- •CRC Calculation
- •Unicode Characters: u:
- •Window Driver And Form Editor
- •Tacit Programming
- •36. Tacit Programs
- •37. First Look At Forks
- •38. Parsing and Execution I
- •39. Parsing and Execution II
- •The Parsing Table
- •Examples Of Parsing And Execution
- •Undefined Words
- •40. Forks, Hooks, and Compound Adverbs
- •Tacit and Compound Adverbs
- •Referring To a Noun In a Tacit Verb
- •41. Readable Tacit Definitions
- •Flatten a Verb: Adverb f.
- •Special Verb-Forms Used in Tacit Definitions
- •43. Common Mistakes
- •Mechanics
- •Programming Errors
- •44. Valedictory
- •45. Glossary
- •46. Error Messages
- •47. Index
22. Calling a DLL Under Windows
Interfacing to a DLL is one thing you can do the old-fashioned way: by getting a copy of a working program and editing it. You can find starter programs in the J distribution. A good one to start with is \system\packages\winapi\registry.ijs. You can glance through this program to see what it does. It starts with
require 'dll'
which defines the verb cd . Calls to the DLL appear in lines like
rc =. 'Advapi32 RegCreateKeyExA i |
i *c i *c i i i *i *i' |
|
cd |
root;key;0;";0;sam;0;(,_1);(,_1) |
(this is a single line in the program; I have shown it here as 2 lines because it won't fit on the page as a single line)
This example exhibits the elements of a call to a DLL. The left operand of cd is a character list describing the function to be called and what arguments it is expecting. Here we are calling the entry point RegCreateKeyExA in the library Advapi32. The sequence of is and *cs describes the interface to the function. The first item in that sequence describes the type of value returned by the function; the other items are the arguments to the function and are a one-for-one rendering of the argument list that would be passed in C. So, the line above is appropriate for calling a function defined with the prototype
int RegCreateKeyExA(int, char *, int , char *, int, int, int, int *, int *);
The descriptors can be c (char), s (short) , i (int), f (float), d (double), j (complex), w (Unicode), l (64-bit integer), x (J integer, either 32 or 64 bits) or n (placeholder—a value of 0 is used and the result is ignored); all but n can be preceded by * to indicate an array of that type. * by itself indicates an array of unspecified type.
The right operand of cd is the actual arguments to be passed to the called function. It must be a list of boxes in which the contents of each box holds one argument to the called function (there is no box to correspond to the returned value). An argument whose description does not include * must correspond to a boxed scalar of the appropriate type. An argument described with * must correspond to a box whose contents are either an array or a boxed memory address (see below). The address of the array or memory area is passed to the DLL and the DLL may modify the array or memory area; this is how you return an array from the function.
s and f are not native J types. A scalar argument described by s or f is converted by the interpreter to the correct form, while an argument described by *s or *f is assumed to point to a memory area with the correct format.
i and d descriptors pose a bit of a problem. They both correspond to numbers in J, but unfortunately it is up to you to ensure that the J internal representation of the number matches the type expected by the DLL. There is no officially-sanctioned way to do this, but as of J6.01 you can use monad <. to ensure that a value is an integer (for i) and monad _&<. to ensure that a value is floating-point (for d).
133
Note that if you get things wrong and the function scribbles outside the bounds of an array, J may crash. Note also that a vector of 0s and 1s is held inside J as a vector of Booleans, which are chars. When the function calls for a vector of ints, J will convert any Boolean to int. In the example above, (,_1) reserved space for an int; (,0) would have worked too but would require a conversion.
When the function returns, its returned value will be boxed and appended to the front of the boxed list that was the right operand of cd to produce the result of the execution of cd . You may use this returned value as you see fit. Any box that contained an array may have had its contents modified by the function; you may open the box to get the changed value.
If J was unable to call the DLL, the cd verb fails with a domain error. You can then execute the sentence cder '' which will return a 2-element list indicating what went wrong. The User Guide gives a complete list of errors; the most likely ones are 4 0 (the number of arguments did not match the number of declarations), 5 x (declaration number x was invalid—the count starts with the declaration of the returned value which is number 0), and 6 x (argument number x did not match its declaration—the count starts with the first argument which is number 0 and must match the second declaration).
Memory Management
Passing arrays into the called function is adequate only for simple functions. If the function expects an argument to be a structure, possibly containing pointers to other structures, you will have to allocate memory for the structures, fill the structures appropriately, and free the memory when it is no longer needed. J provides a set of verbs to support such memory management.
Allocate memory: mema (15!:3)
mema length allocates a memory area of length bytes. The result is the address of the memory area, as an integer. It is 0 if the allocation failed. mema has infinite rank.
You must box the memory address before using it as an operand to cd . Do not box the address for use as an operand to memf, memw, or memr .
Free memory: memf (15!:4)
memf address frees the memory area pointed to by address . address must be a value that was returned by mema . Result of 0 means success, 1 means failure. memf has infinite rank.
Write Into a Memory Area: memw (15!:2)
data memw address,byteoffset,count,type
causes data to be written, starting at an offset of byteoffset from the area pointed to by address, for a length of count items whose type is given by type . type is 2 for characters, 4 for integers, 8 for floating-point numbers, 16 for complex numbers; if
134
omitted, the default is 2 . If type is 2, count may be one more than the length of data to cause a string-terminating NUL (\0) to be written after the data .
Read From a Memory Area: memr (15!:1)
memr address,byteoffset,count,type
produces as its result the information starting at an offset of byteoffset from the area pointed to by address, for a length of count items whose type is given by type . type is 2 for characters, 4 for integers, 8 for floating-point numbers, 16 for complex numbers; if omitted, the default is 2 . The result is a list with count items of the type given by type .If type is 2, count may be _1 which causes the read to be terminated before the first NUL (\0) character encountered.
Filling a Structure: Conversions
To create a structure to pass into a DLL, you must ensure that every byte is in the right place. The way to do this is to convert your nouns to character strings so that you have complete control over how the bytes are packed. J gives you a set of foreigns that will convert your numbers to character strings.
x (3!:4) y converts the atom or list y, which must be integer to within the comparison tolerance, to a character string in which each atom of y occupies 2^x bytes. x may be 1 (2-byte short result), 2 (4-byte long result), or 3 (8-byte result if you have J64). The 'conversion' is merely a change of type: for example, when an integer is converted to 4-byte character, the 32-bit binary code for the integer is not changed, but it is viewed as 4 characters.
To go in the other direction, x is negative: (-x) (3!:4) y splits a string into 2^x-character pieces and calls each piece an integer. If x is 0, the string is split into 2-byte pieces which are construed as representing an unsigned short which is converted to a J number.
3!:5 does a similar job for floating-point values. x (3!:5) y, where x is 1 or 2, produces a string in which each atom of y occupies 2^1+x bytes. y is first converted to a floating-point value of the correct size. Negative values of x convert from string to numeric representation.
Aliasing of Variables
When a noun is assigned the value of another noun, as in a =. b =. 5
a single memory area is used to hold the value common to both nouns, and the two nouns are said to be aliases of each other. Aliasing obviously reduces the time and space used by a computation. The interpreter takes care to ensure that aliasing is invisible to the programmer; if after the statement above we execute
b =. 6
the interpreter will assign the new value to b only, leaving a unchanged. What actually happens is that the new value is created in a data block of its own and the descriptor for the noun b is changed to point to the new block. (Almost all verbs create their outputs in newly-allocated data blocks. As of J5.04 the exceptions are ], [, and , and u} when
135
used in one of the forms that produces in-place modification. Increasing the number of cases recognized for in-place execution is a continuing activity of the J developers).
If there were nothing more to say about aliasing, I would not single it out for mention from among the dozens of performance-improving tricks used by the interpreter. What makes it worth considering is the effect aliasing has when elements outside the J language touch J's nouns. This can occur in two ways: when a noun is mapped to a file and when a noun is modified by a DLL.
Aliasing of Mapped Nouns
When a noun is mapped to a file, the descriptor for the noun points to the file's data and that pointer is never changed even if a value is assigned to the variable: the whole point of mapping the noun to the file is to cause changes in the noun to be reflected in the file, so any assignment to the noun causes the data to be copied into the area that is mapped to the file.
In addition, when b is a noun mapped to a file and is assigned to another noun as with a =. b
the noun a, which is aliased to b, also inherits the 'mapped-to-file' attribute. This behavior is necessary to make mapped files useful, because assignments to x. and y. are implicit whenever a verb is invoked and it would defeat the whole purpose of mapping if the data of the file had to be copied every time the mapped noun was passed to a verb. The combination of aliasing and mapping means that any assignment to a mapped noun also changes the values of all other nouns which share the same mapping: for example, if you pass a mapped noun a as the right operand of a verb that modifies its y., y., a, and the data in the file will all be modified.
Keeping track of the aliasing is the price you pay for using mapped files. If you need to copy a noun making sure you get a fresh, unmapped data block, you must not assign the mapped noun directly, but instead assign the result of some verb that creates its output in a new data block. For example, as of J5.04 the assignment
a =. a:{b
will create a new data block containing the data of b, and a will point to that new block.
Aliasing of DLL Operands
The J interpreter uses aliasing for boxed cells of an array, so that if you execute b =. i. 10000 10000
a =. b;5
item 0 of a simply contains a pointer to the data block of b rather than a fresh copy of the 400MB array. In addition, when a list of boxes is used as the right operand of cd, as in
'dll-spec' cd root;key;0;'';0;sam;0;(,_1);(,_1)
any array operands to the DLL function are passed via a pointer to the data in the boxed list, with no separate copy of the data being made. This means that if the DLL modifies one of its arguments, any nouns aliased to that argument will also be modified: if the DLL function called above modifies its argument 1, the noun key and any noun aliased to key (possibly including private nouns in suspended verbs) will be changed. To
136
protect yourself from such side-effects, you can use (a:{key) in place of key in the invocation of cd .
137