Simplified Pig Latin translator in APL I wrote a simplified pig latin translator in APL and I would like some feedback on it, as I am not sure my implementation is neat enough.

The simplified pig latin translation follows the following rules, where only 'aeiouAEIOU' are considered vowels:


one-letter words have 'way' appended to them; e.g. 'I' becomes 'Iway'
2 or more letter words starting with a vowel have 'ay' appended to them; e.g. 'awesome' becomes 'awesomeay'
2 or more letter words starting with a consonant have the consonants in front of the first vowel moved to the back, and then 'ay' is appended; e.g. 'cool' becomes 'oolcay'


The problem statement (problem 3 of the second easy problem set) specified that input might be a scalar (i.e. a single character) or a (possibly empty) character vector.

This is the code I wrote:

PigLatin ← {
  ⍝ Monadic function expecting character scalar or character vector and returning character vector.
  ⍝ Translates an English sentence into Pig lating.
  ⍝ e.g. 'I always run fast' becomes 'Iway alwaysay unray astfay'.

  vowels ← 'aeiouAEIOU'
  words ← ' ' (≠⊆⊢) ,⍵
  ⍝ Rotate all words until a vowel is at the front.
  rotated ← {⍵ ⌽⍨ ¯1+⊃⍸ ⍵∊vowels}¨ words
  ⍝ Append a 'w' to words of length 1.
  suffixed ← (,∘'w')¨@(1∘=≢¨) rotated
  ⍝ Append 'ay ' to all words, join and drop last ' '.
  ¯1↓∊ (,∘'ay ')¨ suffixed
}


Questions


The basic idea is that I split the input sentence into words, apply the rules to each word and then join them together; this seems sensible, right? This feels like a very standard algorithm but I don't know if APL is suitable for another type of approach.
Following the idea outlined above, my first version had this final line ∊ {⍺' '⍵}/ (,∘'ay')¨ suffixed instead of the current ¯1↓ ∊(,∘'ay ')¨ suffixed; but this meant my code didn't work for empty inputs '' because it tried running {⍺' '⍵}/ on an empty vector and raised a DOMAIN ERROR. My workaround for this was appending 'ay ' to every word, instead of just 'ay' and then dropping the final extra ' ' with ¯1↓;


Is this a good way of handling the edge case ''?
Would it be better if I had a dfn guard for the '' case?
Would you handle it in a different way?

Is ≠⊆⊢ an idiom in APL to split the right vector on the left arguments? It even shows in the tooltip for the Partition ⊆ glyph.
Any further comments, suggestions, etc that don't necessarily address my questions are also welcome.
Overall

Your approach is fine, and your code (including ≠⊆⊢) is fairly idiomatic. Handling the edge case by always appending a space and dropping it at the end is standard procedure, so no, you don't need a branch here.

Split up your code in sections

You begin with setting up a couple of constants. Consider inserting a blank line to gently separate these from the main code.

Inline comments

Well-written APL code tends to have short lines, so there's generally enough space to include comments. This allows a simple hierarchy of comments:


Full-line comments for introductions to sections.
End-of-line comments for code explanations.


Consistency

You use intermediary variables for rotating and appending "w" but not for appending "ay ".

Array approach to conditional concatenation

(,∘'w')¨@(1∘=≢¨) does two loops:


(1∘=≢¨) (which could be (1=≢¨) too) to determine which words need appending.
(,∘'w')¨ (which could be ,∘'w'¨ or 'w',¨⍨ too) to do the appending.


A more holistic array approach is to append to every word, and instead modify what is appended. That is, collapse the appendix to shape 0 for words of length different from 1. Rephrased, this becomes keep the appendix as-is for words of length equal to 1, or 'w'/⍨1=≢. It becomes a "conditionally" append function in the form of ⊢,'w'/⍨1=≢, which you could then apply to each with (⊢,'w'/⍨1=≢)¨. However, you might want to…

Reduce ¨pepper¨

Some APLers call code with too many ¨s "too peppered" referring to the many tiny dots in foods that contain lots of black pepper. You may want to consider fusing the loops by defining the constituent transformation functions and applying them together in a single loop. Appropriate naming of the functions allows shortening the comments to become clarifications of the names, which can even make a comment obsolete.

Revised code

PigLatin←{
  ⍝ Monadic function expecting character scalar or character vector and returning character vector.
  ⍝ Translates an English sentence into Pig Latin.
  ⍝ e.g. 'I always run fast' becomes 'Iway alwaysay unray astfay'.

  vowels ← 'aeiouAEIOU'
  Words ← ' '(≠⊆⊢),

  Rotate ← {⍵ ⌽⍨ ¯1+⊃⍸ ⍵∊vowels}  ⍝ all words until a vowel is at the front
  Add_w ← ⊢,'w'/⍨1=≢              ⍝ if word has length 1
  Add_ay ← ,∘'ay '

  ¯1↓∊ Add_ay∘Add_w∘Rotate¨ Words ⍵
}


Other approaches

Writing APL is fun*, so APLers tend to write everything from scratch every time, instead of using the tools at hand. In this case, Perl-style regular expressions might be a help.

Using regex to process words

It is easy to apply a function to each word using '\w+' ⎕R {MyFn ⍵.Match}:

PigLatinWord←{
  vowels ← 'aeiouAEIOU'

  Rotate ← {⍵ ⌽⍨ ¯1+⊃⍸ ⍵∊vowels}
  W ← ⊢,'w'/⍨1=≢
  Ay ← ,∘'ay'

  Ay W Rotate ⍵
}
PigLatinRegex ← '\w+' ⎕R {PigLatinWord ⍵.Match}


The \w+ pattern matches runs of word characters.

If this was a common thing, we could define a utility operator that applies a text transformation on words:

_OnWords ← {'\w+' ⎕R (⍺⍺{⍺⍺ ⍵.Match}) ⍵}
PigLatinOnWords ← PigLatinWord _OnWords


An alternative coding which avoids passing the operand multiple times:

_OnWords ← {'\w+' ⎕R (⍺⍺⍎∘'Match') ⍵}
PigLatinOnWords ← PigLatinWord _OnWords


Doing the entire job with regexes

That said, ⎕R actually has a fancy feature that allows running multiple search patterns in parallel (for every starting character the patterns are tested in order) each with their own substitution pattern. This makes it easy to catch and process edge cases before the main transformations have a chance to kick in.

PigLatinRegexes ← '\w\b' '([^aeiou ]*)(\w+)' ⎕R '&amp;way' '\2\1ay' ⍠1


Here, we have two patterns:


\w\b word character, word boundary: a 1-character word.
([^aeiou ]*)(\w+) any consonants (group 1), word characters (group 2): any other word


And the corresponding substitution patterns:


&amp;way the match followed by "way"
\2\1ay group 2, group 1 (which can be empty), "ay"


Finally, ⍠1 makes the derived function ignore case.