refactoringjtacit-programming

Systematically extract noun arguments from J expression


What is the systematic approach to extracting nouns as arguments from an expression in J? To be clear, an expression containing two literals should become a dyadic expression with the left and right arguments used instead of the literals.

I'm trying to learn tacit style so I prefer not to use named variables if it is avoidable.

A specific example is a simple die roll simulator I made:

   >:?10#6    NB. Roll ten six sided dice.
2 2 6 5 3 6 4 5 4 3
   >:?10#6
2 1 2 4 3 1 3 1 5 4

I would like to systematically extract the arguments 10 and 6 to the outside of the expression so it can roll any number of any sized dice:

   d =. <new expression here>
   10 d 6  NB. Roll ten six sided dice.
1 6 4 6 6 1 5 2 3 4
   3 d 100  NB. Roll three one hundred sided dice.
7 27 74

Feel free to illustrate using my example, but I'm looking to be able to follow the procedure for arbitrary expressions.

Edit: I just found out that a quoted version using x and y can be automatically converted to tacit form using e.g. 13 : '>:?x#y'. If someone can show me how to find the definition of 13 : I might be able to answer my own question.


Solution

  • If your goal is to learn tacit style, it's better that you simply learn it from the ground up rather than try to memorize an explicit algorithm—J4C and Learning J are good resources—because the general case of converting an expression from explicit to tacit is intractable.

    Even ignoring the fact that there have been no provisions for tacit conjunctions since J4, in the explicit definition of a verb you can (1) use control words, (2) use and modify global variables, (3) put expressions containing x and/or y as the operands of an adverb or conjunction, and (4) reference itself. Solving (1), (3), or (4) is very hard in the general case and (2) is just flat out impossible.*

    If your J sentence is one of a small class of expressions, there is an easy way to apply the fork rules make it tacit, and this is what is more or less what is implemented in 13 :. Recall that

    Notice how forks use their center verbs as the 'outermost' verb: Fork gives a dyadic application of g, while Capped Fork gives a monadic one. This corresponds exactly to the two modes of application of a verb in J, monadic and dyadic. So a quick-and-dirty algorithm for making tacit a "dyadic" expression might look like the following, for F G H verbs and N nouns:

    1. Replace x with (x [ y) and y with (x ] y). (Left/Right)
    2. Replace any other noun n with (x N"_ y)
    3. If you see the pattern (x F y) G (x H y), replace it with x (F G H) y. (Fork)
    4. If you see the pattern G (x H y), replace it with x ([: G H) y. (*Capped Fork()
    5. Repeat 1 through 4 until you attain the form x F y, at which point you win.
    6. If no more simplifications can be performed and you have not yet won, you lose.

    A similar algorithm can be derived for "monadic expressions", expressions only dependent on y. Here's a sample derivation.

    <. (y - x | y) % x                          NB. start
    <. ((x ] y) - (x [ y) | (x ] y)) % (x [ y)  NB. 1
    <. ((x ] y) - (x ([ | ]) y)) % (x [ y)      NB. 3
    <. (x (] - ([ | ])) y) % (x [ y)            NB. 3
    <. x ((] - ([ | ])) % [) y                  NB. 3
    x ([: <. ((] - ([ | ])) % [)) y             NB. 4 and we win
    

    This neglects some obvious simplifications, but attains the goal. You can mix in various other rules to simplify, like the long train rule—if Train is a train of odd length then (F G (Train)) are equivalent (F G Train)—or the observation that x ([ F ]) y and x F y are equivalent. After learning the rules, it shouldn't be hard to modify the algorithm to get the result [: <. [ %~ ] - |, which is what 13 : '<. (y - x | y) % x' gives.

    The fail condition is attained whenever an expression containing x and/or y is an operand to an adverb or conjunction. It is sometimes possible to recover a tacit form with some deep refactoring, and knowledge of the verb and gerundial forms of ^: and }, but I am doubtful that this can be done programmatically.

    This is what makes (1), (3), and (4) hard instead of impossible. Given knowledge of how $: works, a tacit programmer can find a tacit form for, say, the Ackermann function without too much trouble, and a clever one can even refactor that for efficiency. If you could find an algorithm doing that, you'd obviate programmers, period.

       ack1 =: (1 + ])`(([ - 1:) $: 1:)`(([ - 1:) $: [ $: ] - 1:)@.(, i. 0:)
       ack2 =: $: ^: (<:@[`]`1:) ^: (0 < [) >:
       3 (ack1, ack2) 3
    61 61
       TimeSpace =: 6!:2, 7!:2@]  NB. iterations TimeSpace code
       10 TimeSpace '3 ack1 8'
    2.01708 853504
       10 TimeSpace '3 ack2 8'
    0.937484 10368
    

    * This is kind of a lie. You can refactor the entire program involving such a verb through some advanced voodoo magic, cf. Pepe Quintana's talk at the 2012 J Conference. It isn't pretty.