dukereg
dukereg

Reputation: 722

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.

Upvotes: 2

Views: 173

Answers (2)

algorithmshark
algorithmshark

Reputation: 267

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

  • (F G H) y is (F y) G (H y), and x (F G H) y is (x F y) G (x H y) (Monad/Dyad Fork)
  • ([: G H) y is G (H y), and x ([: G H) y is G (x H y) (Monad/Dyad Capped Fork)
  • x [ y is x, x ] y is y, and both of [ y and ] y are y (Left/Right)

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.

Upvotes: 5

tangentstorm
tangentstorm

Reputation: 7315

13 : is documented in the vocabulary or NuVoc under : (Explicit).

The basic idea is that the value you want to be x becomes [ and the value you want to be y becomes ]. But as soon as the the rightmost token changes from a noun (value) to a verb like [ or ], the entire statement becomes a train, and you may need to use the verb [: or the conjunctions @ or @: to restore the composition behavior you had before.

You can also replace the values with the actual names x and y, and then wrap the whole thing in ((dyad : ' ... ')). That is:

>:?10#6    NB. Roll ten six sided dice.

can become:

10 (dyad : '>: ? x # y') 6  NB. dyad is predefined. It's just 4.

If you only need the y argument, you can use monad, which is prefined as 3. The name verb is also 3. I tend to use verb : when I provide both a monadic and dyadic version, and monad when I only need the monadic meaning.

If your verb is a one-liner like this, you can sometimes convert it automatically to tacit form by replacing the 3 or 4 with 13.

I have some notes on factoring verbs in j that can help you with the step-by-step transformations.

addendum: psuedocode for converting a statement to tacit dyad

This only covers a single statement (one line of code) and may not work if the constant values you're trying to extract are being passed to a conjunction or adverb.

Also, the statement must not make any reference to other variables.

  • Append [ x=. xVal [ y =. yVal to the statement.
  • Substitute appropriate values for xVal and yVal.
  • Rewrite the original expression in terms of the new x and y.
  • rewrite statement [ x=. xVal [ y=. yVal as:

newVerb =: (4 : 0)
  statement ] y   NB. we'll fill in x later.
)
(xVal) newVerb yVal

Now you have an explicit definition in terms of x and y. The reason for putting it on multiple lines instead of using x (4 : 'expr') y is that if expr still contains a string literal, you will have to fiddle with escaping the single quotes.

Converting the first noun

Since you only had a pipeline before, the rightmost expression inside statement must be a noun. Convert it to a fork using the following rules:

  • y(])
  • x]x ([)
  • _, __, _9 ... 9(_:), (__:), (_9:) ... (9:)
  • nn"_ (for any other arbitrary noun)

This keeps the overall meaning the same because the verb you've just created is invoked immediately and applied to the [ y.

Anyway, this new tacit verb in parentheses becomes the core of the train you will build. From here on out, you work by consuming the rightmost expression in the statement, and moving it inside the parentheses.

Fork normal form

From here on out, we will assume the tacit verb we're creating is always a fork.

This new tacit verb isn't actually a fork, but we will pretend it is, because any single-token verb can be rewritten as a fork using the rule:

v → ([: ] v).

There is no reason to actually do this transformation, it's just so I can simplify the rule below and always call it a fork.

We will not use hooks because any hook can be rewritten as a fork with the rule:

(u v) → (] u [: v ])

The rules below should produce trains in this form automatically.

Converting the remaining tokens

Now we can use the following rules to convert the rest of the original pipeline, moving one item at a time into the fork.

For all of these rules, the (]x)? isn't J syntax. It means the ]x may or may not be there. You can't put the ] x in until you transform a usage of x without changing the meaning of the code. Once you transform an instance of x, the ]x is required.

Following the J convention, u and v represent arbitrary verbs, and n is an arbitrary noun. Note that these include verbs

tokens y u (]x)? (fork) ] y  →  tokens   (]x)? (]  u fork) ] y
tokens x u (]x)? (fork) ] y  →  tokens    ]x   ([  u fork) ] y
tokens n u (]x)? (fork) ] y  →  tokens   (]x)? (n  u fork) ] y
tokens u v (]x)? (fork) ] y  →  tokens u (]x)? ([: v fork) ] y

There are no rules for adverbs or conjunctions, because you should just treat those as part of the verbs. For example +:^:3 should be treated as a single verb. Similarly, anything in parentheses should be left alone as a single phrase.

Anyway, keep applying these rules until you run out of tokens.

Cleanup

You should end up with:

newVerb =: (4 : 0)
  ] x (fork) ] y
)
(xVal) newVerb yVal

This can be rewritten as:

(xVal) (fork) yVal

And you are done.

Upvotes: 3

Related Questions