Search code examples
collectionsaggregationenumerationsmalltalkreduction

Code for #into: is related to the code for #inject:into: , but must be solving a different problem. Explain the difference?


What does this do, and is there a simpler way to write it?

Collection>>into: a2block
    | all pair |
    all := ([:allIn :each| allIn key key: allIn value. each])
           -> (pair := nil -> a2block).
    pair key: all.
    self inject: all 
         into: [:allIn :each| allIn value: (allIn key value: allIn value value: each). allIn].
    ^all value

Solution

  • If you want to find out, the best way is to try it, possibly step by step thru debugger.

    For example, try:

    (1 to: 4) into: #+.
    (1 to: 4) into: #*.
    

    You'll see a form of reduction.

    So it's like inject:into: but without injecting anything.
    The a2block will consume first two elements, then result of this and 3rd element, etc...
    So we must understand the selector name as inject:into: without inject:: like the implementation, it's already a crooked way to name things.

    This is implemented in Squeak as reduce:, except that reduce: would be well too obvious as a selector, and except that it would raise an Error if sent to an empty collection, contrarily to this which will answer the strange recursive artifact.

    The all artifact contains a block to be executed for reduction as key, and current value of reduction as value. At first step, it arranges to replace the block to be executed with a2block, and the first value by first element. But the recursive structure used to achieve the replacement is... un-necessarily complex.

    A bit less obfuscated way to write the same thing would be:

    into: a2block
        | block |
        self ifEmpty: [self error: 'receiver of into: shall not be empty'].
        block := [:newBlock :each| block := newBlock. each].
        ^self inject: a2block into: [:accum :each| block value: accum value: each]
    

    That's more or less the same principle: at first iteration, the block is replaced by the reduction block (a2block), and first element is accumulated.

    The reduction only begins at 2nd iteration.

    The alternative is to check for first iteration inside the loop... One way to do it:

    into: a2block
        | first |
        self ifEmpty: [self error: 'receiver of into: shall not be empty'].
        first := Object new.
        ^self inject: first into: [:accum :each |
            accum == first
                ifTrue: [each]
                ifFalse: [a2block value: accum value: each]].
    

    There are many other ways to write it, more explicitely using a boolean to test for first iteration...