4 from notebook_preamble import D, DefinitionWrapper, J, V, define
6 On "Two Exercises Found in a Book on Algorithmics"
7 ==================================================
11 Define ``scan`` in terms of a reduction.
12 ----------------------------------------
14 Problem I. The reduction operator ``/`` of APL takes some binary
15 operator ``⨁`` on its left and a vector ``x`` of values on its
16 right. The meaning of ``⨁/x`` for ``x = [a b ... z]`` is the value
17 ``a⨁b⨁...⨁z``. For this to be well-defined in the absence of
18 brackets, the operation ``⨁`` has to be associative. Now there is
19 another operator ``\`` of APL called ``scan``. Its effect is closely
20 related to reduction in that we have:
24 ⨁\x = [a a⨁b a⨁b⨁c ... a⨁b⨁...⨁z]
26 The problem is to find some definition of ``scan`` as a reduction.
27 In other words, we have to find some function ``f`` and an operator
32 ⨂\x = f(a)⨂f(b)⨂...⨂f(z)
34 Designing the Recursive Function
35 --------------------------------
37 Ignoring the exact requirements (finding ``f`` and ``⨂``) can we
38 implement ``scan`` as a hylomorphism in Joy?
40 Looking at the forms of hylomorphism, ``H3`` is the one to use:
45 If the combiner and the generator both need to work on the current value
46 then ``dup`` must be used, and the generator must produce one item
47 instead of two (the b is instead the duplicate of a.)
51 H3 == [P] [pop c] [[G] dupdip] [dip F] genrec
53 ... a [G] dupdip [H3] dip F
57 ... a′ [G] dupdip [H3] dip F a F
58 ... a′ G a′ [H3] dip F a F
59 ... a″ a′ [H3] dip F a F
61 ... a″ [G] dupdip [H3] dip F a′ F a F
62 ... a″ G a″ [H3] dip F a′ F a F
63 ... a‴ a″ [H3] dip F a′ F a F
64 ... a‴ H3 a″ F a′ F a F
65 ... a‴ pop c a″ F a′ F a F
74 We're building a list of values so this is an "anamorphism". (An
75 anamorphism uses ``[]`` for ``c`` and ``swons`` for ``F``.)
79 scan == [P] [pop []] [[G] dupdip] [dip swons] genrec
85 scan == [P] [pop []] [[G] dupdip [scan] dip swons] ifte
87 On the recursive branch ``[G] dupdip`` doesn't cut it:
91 [1 2 3] [G] dupdip [scan] dip swons
92 [1 2 3] G [1 2 3] [scan] dip swons
97 At this point, we want the copy of ``[1 2 3]`` to just be ``1``, so we
102 scan == [P] [pop []] [[G] dupdip first] [dip swons] genrec
104 [1 2 3] [G] dupdip first [scan] dip swons
105 [1 2 3] G [1 2 3] first [scan] dip swons
106 [1 2 3] G 1 [scan] dip swons
111 Now what does ``G`` have to do? Just apply ``⨁`` to the first two terms
124 Which tells us that the predicate ``[P]`` must guard against lists with
125 less that two items in them:
131 Let's see what we've got so far:
135 scan == [P ] [pop []] [[G] dupdip first] [dip swons] genrec
136 scan == [size 1 <=] [pop []] [[[F] infra] dupdip first] [dip swons] genrec
138 Handling the Last Term
139 ~~~~~~~~~~~~~~~~~~~~~~
141 This works to a point, but it throws away the last term:
145 J('[1 2 3] [size 1 <=] [pop []] [[[+] infra] dupdip first] [dip swons] genrec')
153 Hmm... Let's take out the ``pop`` for a sec...
157 J('[1 2 3] [size 1 <=] [[]] [[[+] infra] dupdip first] [dip swons] genrec')
165 That leaves the last item in our list, then it puts an empty list on the
166 stack and ``swons``'s the new terms onto that. If we leave out that
167 empty list, they will be ``swons``'d onto that list that already has the
172 J('[1 2 3] [size 1 <=] [] [[[+] infra] dupdip first] [dip swons] genrec')
187 [⨁] scan == [size 1 <=] [] [[[⨁] infra] dupdip first] [dip swons] genrec
193 == [size 1 <=] [] [[[⨁] infra] dupdip first] [dip swons] genrec
194 == [[[⨁] infra] dupdip first] [size 1 <=] [] roll< [dip swons] genrec
195 == [[⨁] infra] [dupdip first] cons [size 1 <=] [] roll< [dip swons] genrec
196 == [⨁] [infra] cons [dupdip first] cons [size 1 <=] [] roll< [dip swons] genrec
202 scan == [infra] cons [dupdip first] cons [size 1 <=] [] roll< [dip swons] genrec
206 define('scan == [infra] cons [dupdip first] cons [size 1 <=] [] roll< [dip swons] genrec')
210 J('[1 2 3 4] [+] scan')
220 J('[1 2 3 4] [*] scan')
230 J('[1 2 3 4 5 6 7] [neg +] scan')
241 Define a line to be a sequence of characters not containing the
242 newline character. It is easy to define a function ``Unlines`` that
243 converts a non-empty sequence of lines into a sequence of characters
244 by inserting newline characters between every two lines.
246 Since ``Unlines`` is injective, the function ``Lines``, which
247 converts a sequence of characters into a sequence of lines by
248 splitting on newline characters, can be specified as the inverse of
251 The problem, just as in Problem 1. is to find a definition by
252 reduction of the function ``Lines``.
256 Unlines = uncons ['\n' swap + +] step
260 J('["hello" "world"] uncons ["\n" swap + +] step')
268 Again ignoring the actual task let's just derive ``Lines``:
272 "abc\nefg\nhij" Lines
273 ---------------------------
276 Instead of ``P == [size 1 <=]`` we want ``["\n" in]``, and for the
277 base-case of a string with no newlines in it we want to use ``unit``:
281 Lines == ["\n" in] [unit] [R0] [dip swons] genrec
282 Lines == ["\n" in] [unit] [R0 [Lines] dip swons] ifte
288 "a \n b" R0 [Lines] dip swons
289 "a \n b" split-at-newline swap [Lines] dip swons
290 "a " " b" swap [Lines] dip swons
291 " b" "a " [Lines] dip swons
292 " b" Lines "a " swons
300 R0 == split-at-newline swap
302 Lines == ["\n" in] [unit] [split-at-newline swap] [dip swons] genrec
307 This is all good and well, but in the paper many interesting laws and
308 properties are discussed. Am I missing the point?
312 0 [a b c d] [F] step == 0 [a b] [F] step 0 [c d] [F] step concat
314 For associative function ``F`` and a "unit" element for that function,
315 here represented by ``0``.
317 For functions that don't have a "unit" we can fake it (the example is
318 given of infinity for the ``min(a, b)`` function.) We can also use:
322 safe_step == [size 1 <=] [] [uncons [F] step] ifte
328 safe_step == [pop size 1 <=] [pop] [[uncons] dip step] ifte
330 [a b c] [F] safe_step
331 ---------------------------
334 To limit ``F`` to working on pairs of terms from its domain.