bool ← ##.isdfn name ⍝ Test for D function. [isdfn] takes a character vector argument [name] and returns 1 if it refers to a D function or operator. NB: With Dyalog V11, this function became trivial. ⎕NC with a nested argument now returns sub-classes, which distinguish various types of functions, including D-fns. Prior to V11, the task was more challenging ... Technical notes: The many ways to write this function seem to split into two broad categories: "honest" and "sneaky". The "honest" solutions attempt to analyse the source code of the function, whereas the "sneaky" ones try to detect differences in the effect of various system operations. This is not to say that an honest solution is any "better" than a sneaky one. Honest ------ The main difficulty with looking at the source code, is that the header line of an _ambivalent_ traditional function in Dyalog contains braces: rslt←{larg} func rarg ... which rules out testing for the string '←{' in the header. Checking that the _last_ character in the function is a '}' also comes unstuck, because a trad-fn could finish by defining a D-fn: rslt←{larg} func rarg ... dup←{⍵ ⍵} The water is further muddied because Dyalog header lines may contain comments, which could make a trad-fn resemble a D-fn. rslt←{larg} func rarg ⍝ {} and the first line in a D-fn might contain character literals, which could make it look a little like an ambivalent trad-fn. dfn←{larg'}' func rarg ... The "honest" approach seems to boil down to: It's a D-fn iff: the ⎕CR is a matrix, whose first line, (ignoring comments and character literals) contains a '{' and nothing follows any matching '}'. Examples of this approach include: Phil Last's: isdfn←{ ⍝ is fn/op named in (⍵) a dfn/dop? {1=⍴⍴⍵:0 ⍝ vector ⎕CR - derived function {('}'=⊃⌽⍵)∨≠/+⌿⍵∘.='{}' ⍝ where do braces balance? }{⍵/⍨∧\⍵≠'⍝' ⍝ remove remarks }{⍵/⍨~≠\⍵='''' ⍝ remove literals }⍵[?1;]~' ' ⍝ first line }⎕CR ⍵ ⍝ foreign needs qualified name } Reima Naumanen's "one-liner" (broken up a bit to show what's happening): isdfn←{ { ('}'=⎕IO⊃⍵)∨≠/('{}'∘.=⍵)+.×~2|+\''''=⍵ }(⌽{ ⍵/⍨∧\~⍵='⍝' }⎕IO⊃⎕NR ⍵)~' ' } Veli-Matti Jantunen produced a whole suite of functions! isdfn_W0←{ ⍝| Add comment column to the function body, ⍝| fix the fn in an unnamed namespace and check for the ⍝| last non-blank - dfn doesn't have the last lamp! c←⎕CR ⍵ ⋄ '}'≠¯1↑(,c)~' ':0 n←⎕NS'' ⋄ '}'=⍬⍴¯1↑n.(,⎕CR ⎕FX ##.c,'⍝')~' ' } isdfn_W1←{ ⍝| Add blank column to the function body, ⍝| fix the fn in an unnamed namespace and check for the ⍝| header - dfn starts with "dfn←{" f←⍵~' ' ⋄ c←⎕CR f ⋄ (f,'←{')≢(2+⍴f)⍴c:0 n←⎕NS'' ⋄ (f,'←{')≡(2+⍴f)⍴n.(⎕CR ⎕FX' ',##.c) } isdfn_W2←{ ⍝| Count the brace balance in the function body. ⍝| If the balance is fulfilled only with the last ⍝| right brace which ends the function body -> dfn! f←⍵~' ' ⋄ c←⎕CR f ⋄ (f,'←{')≢(2+⍴f)⍴c:0 ⎕ML←3 ⋄ g←{(~≠\⍵='''')/⍵}¨↓c r←∊{(∧\⍵≠'⍝')/⍵}¨g s←{(+\⍵='{')-+\⍵='}'}(1+⍴f)↓r~' ' (1=+/s=0)>0∊¯1↓s } isdfn_W3←{ ⍝| A variant of brace balance algorithm. ⍝| This time only the header is checked. f←⍵~' ' ⋄ c←⎕CR f ⋄ (f,'←{')≢(2+⍴f)⍴c:0 h←{(~≠\⍵='''')/⍵}{(⍵⍳'⍝')↑⍵}c[⎕IO;] 1≥+/0={(+\⍵='{')-+\⍵='}'}(1+⍴f)↓h~' ' } isdfn_W4←{ ⍝| If a) the body starts with fun←{, and ⍝| b) the following part between braces is a valid name ⍝| => the header is that of a trad fn/operator. ⍝| Alas, we have to check for the D function df←{variable}, too :( f←⍵~' ' ⋄ v←¯7↓6↓⎕VR f (f,'←{')≢(2+⍴f)⍴v:0 ¯1=⎕NC(2+⍴f)↓(¯1+v⍳'}')↑v:1 (⎕IO+(∨/⎕TC∊v)<'}'=¯1↑v)⊃0 1 } isdfn_W5←{ ⍝| This time we'll just check the header: ⍝| If the brace balance is met, the fn is traditional... ⍝| but we must take care for the one-line dfns, too (with drop). n←⎕NR f ⋄ 0∊⍴n:0 2|+/'{}'∊⍨{(~≠\⍵='''')/⍵}{(⍵⍳'⍝')↑⍵}(-1=⍴n)↓(⎕IO⊃n)~' ' } isdfn_W6←{ ⍝| A slightly different variant from isdfn_W4 v←¯7↓6↓⎕VR ⍵ ⋄ '}'≠¯1↑v:0 ⍝ dfn _must_ end with } f←⍵~' ' ⋄ (f,'←{')≢(2+⍴f)⍴v:0 ⍝ ..and start with dfn←{ ⍝ now we have a dfn OR trad fn with a header like ⍝ fun←{left} fun right.. ⍝ mop←{left}( mop R) right.. ⍝ dop←{left}(L dop R) right.. ¯1=⎕NC(2+⍴f)↓(¯1+v⍳'}')↑v:1 ⍝ not {left} ? ~∨/⎕TC∊v ⍝ dfn←{variable} case } Sneaky ------ The alternative is to discover a "dark little corner" in the interpreter that behaves in a different way for D- and trad- functions. For example: - If auto-format is on, the ⎕CR of a trad-fn starts with a blank. - ⎕REFS of a D-fn bombs with a DOMAIN ERROR. - Dyadic ⎕STOP on a D-fn has no effect. These all seem to work; are in general simpler and quicker than the honest way but are dangerous in that the behaviour on which they rely is susceptible to be- ing "fixed". Some (slightly amended to make them ⎕io- proof) examples are: Jim Ryan's: isdfn←{' '≠⍬⍴⎕CR ⍵} Ray Cannon's: isdfn←{ 11::1 0⊣⎕REFS ⍵ } VMJ's: isdfn_W7←{ 0∊⍴⎕VR ⍵:0 ⋄ ~0∊⍴⎕MONITOR ⍵:0 ~0∊⍴0 ⎕MONITOR ⍵:0⊣⍬ ⎕MONITOR ⍵ 1 2≡2⍴1 ⎕AT ⍵ } and this from someone who, understandably, prefers to remain anonymous: isdfn←{⎕STOP∘⍵{(⍺⍺ ⍵)⊢⍬≡⍺⍺ 0}⎕STOP·⍵} John Daintree notes that ⎕fx is intolerant of unbalanced braces when fixing a dfn. He exploits this by appending an extra brace and attempting a re-fix, which fails for a dfn and succeeds for a trad-fn. In order to avoid damaging an exist- ing traditional function, the ⎕fx is applied in a temporary namespace. Note that this version classifies a named derived function (sum←+/), or even a variable (pi←3.142), as a dfn. It is safe therefore, only in a context where the subject is known to be either a trad-fn or a d-fn. isdfn←{⍬≡0⍴(⎕ns'').⎕fx'}',⍨⎕nr ⍵} Paul Mansour's solution seems to be "sneaky but safe" as it relies on a behav- ioural difference that is there by design, rather than accident: If a D-fn is assigned a name, the ⎕CR/⎕NR/⎕VR of the function reflects that name. In particular, if a second name is assigned, the ⎕CR/.. using the new name reflects that new name. This is not the case with a trad-fn where the ⎕CR remem- bers its "original" name: d1←{⍵ ⍵} ⋄ d2←d1 ⋄ ⎕cr'd2' ⋄ ≡/⎕cr¨'d1' 'd2' ⍝ D-fn. d2←{⍵ ⍵} 0 ⎕fx,⊂'zz←t1 zz' ⋄ t2←t1 ⋄ ⎕cr't2' ⋄ ≡/⎕cr¨'t1' 't2' ⍝ Trad-fn. rg←t1 rg 1 Paul's solution exploits this distinction in the following way: isdfn←{ ⍝ Test for D function. 1 ¯2 0≢⎕IO⊃⎕AT ⍵:0 ⍝ No exp result, Not ambivalent, can't be D ⍙←⍎⍵ ⍝ Give it a new name ≢/⎕VR¨'⍙'⍵ ⍝ Compare } There is one small problem with the approach. What if the subject function happens to be called '⍙'? In this case, whatever the function type of the argu- ment, the ⎕VR's will match and [isdfn] will return 0. We need to find a new name that is guaranteed to be distinct from ⍵. One solution is to use a temporary name whose pedigree is known. "isdfn" fits the bill! isdfn←{ ⍝ Test for D function. 1 ¯2 0≢⎕IO⊃⎕AT ⍵:0 ⍝ No exp result, Not ambivalent, can't be D. 'isdfn'≡⍵~' ':1 ⍝ I'm a D-fn! isdfn←⍎⍵ ⍝ Give it a new name. ≢/⎕VR¨'isdfn'⍵ ⍝ Compare. } This solution works in all cases but is slightly precarious. If we wanted to re- name the function: IsDfn←isdfn, we would have to remember to make corresponding changes to both occurrences of the string 'isdfn' in the code body. A more robust but more complex solution would be to have an alternative temporary name in reserve in case there's a clash. VMJ suggests: isdfn←{ ⍝ Test for D function. 0∊⍴⎕VR ⍵:0 ⍝ Irrepresentable: not a dfn. 1 ¯2 0≢⎕IO⊃⎕AT ⍵:0 ⍝ No exp rslt, not ambiv, can't be D. f0←f1←⍎⍵ ⍝ Choose new names and (⎕VR ⍵)≢⎕VR'f',⍕'f1'≢⍵~' ' ⍝ compare. } Choosing names 'f0' and 'f1' ensures that a name clash is impossible. If the subject function is one of these, it can't be the other one. Notice that these latter solutions, correctly or incorrectly, fail to classify a D _operator_ as a 'dfn'. This version of ##.isdfn includes operators: isdfn←{ ⍝ Test for Dfns/Dops. 0.2=1|⎕NC⊂⍵ } Back to: contents Back to: Workspaces