If I recall correctly, there is an alternate protocol that looks more like
xtreams or the traditional select/collect iterations.

On 2 June 2017 at 21:12, Stephane Ducasse <stepharo.s...@gmail.com> wrote:

> I have a design question
>
> why the library is implemented in functional style vs messages?
> I do not see why this is needed. To my eyes the compact notation
> goes against readibility of code and it feels ad-hoc in Smalltalk.
>
>
> I really prefer
>
> square := Map function: #squared.
> take := Take number: 1000.
>
> Because I know that I can read it and understand it.
> From that perspective I prefer Xtreams.
>
> Stef
>
>
>
>
>
>
>
>
>
> On Wed, May 31, 2017 at 2:23 PM, Steffen Märcker <merk...@web.de> wrote:
>
>> Hi,
>>
>> I am the developer of the library 'Transducers' for VisualWorks. It was
>> formerly known as 'Reducers', but this name was a poor choice. I'd like to
>> port it to Pharo, if there is any interest on your side. I hope to learn
>> more about Pharo in this process, since I am mainly a VW guy. And most
>> likely, I will come up with a bunch of questions. :-)
>>
>> Meanwhile, I'll cross-post the introduction from VWnc below. I'd be very
>> happy to hear your optinions, questions and I hope we can start a fruitful
>> discussion - even if there is not Pharo port yet.
>>
>> Best, Steffen
>>
>>
>>
>> Transducers are building blocks that encapsulate how to process elements
>> of a data sequence independently of the underlying input and output
>> source.
>>
>>
>>
>> # Overview
>>
>> ## Encapsulate
>> Implementations of enumeration methods, such as #collect:, have the logic
>> how to process a single element in common.
>> However, that logic is reimplemented each and every time. Transducers make
>> it explicit and facilitate re-use and coherent behavior.
>> For example:
>> - #collect: requires mapping: (aBlock1 map)
>> - #select: requires filtering: (aBlock2 filter)
>>
>>
>> ## Compose
>> In practice, algorithms often require multiple processing steps, e.g.,
>> mapping only a filtered set of elements.
>> Transducers are inherently composable, and hereby, allow to make the
>> combination of steps explicit.
>> Since transducers do not build intermediate collections, their composition
>> is memory-efficient.
>> For example:
>> - (aBlock1 filter) * (aBlock2 map)   "(1.) filter and (2.) map elements"
>>
>>
>> ## Re-Use
>> Transducers are decoupled from the input and output sources, and hence,
>> they can be reused in different contexts.
>> For example:
>> - enumeration of collections
>> - processing of streams
>> - communicating via channels
>>
>>
>>
>> # Usage by Example
>>
>> We build a coin flipping experiment and count the occurrence of heads and
>> tails.
>>
>> First, we associate random numbers with the sides of a coin.
>>
>>     scale := [:x | (x * 2 + 1) floor] map.
>>     sides := #(heads tails) replace.
>>
>> Scale is a transducer that maps numbers x between 0 and 1 to 1 and 2.
>> Sides is a transducer that replaces the numbers with heads an tails by
>> lookup in an array.
>> Next, we choose a number of samples.
>>
>>     count := 1000 take.
>>
>> Count is a transducer that takes 1000 elements from a source.
>> We keep track of the occurrences of heads an tails using a bag.
>>
>>     collect := [:bag :c | bag add: c; yourself].
>>
>> Collect is binary block (reducing function) that collects events in a bag.
>> We assemble the experiment by transforming the block using the
>> transducers.
>>
>>     experiment := (scale * sides * count) transform: collect.
>>
>>   From left to right we see the steps involved: scale, sides, count and
>> collect.
>> Transforming assembles these steps into a binary block (reducing function)
>> we can use to run the experiment.
>>
>>     samples := Random new
>>                   reduce: experiment
>>                   init: Bag new.
>>
>> Here, we use #reduce:init:, which is mostly similar to #inject:into:.
>> To execute a transformation and a reduction together, we can use
>> #transduce:reduce:init:.
>>
>>     samples := Random new
>>                   transduce: scale * sides * count
>>                   reduce: collect
>>                   init: Bag new.
>>
>> We can also express the experiment as data-flow using #<~.
>> This enables us to build objects that can be re-used in other experiments.
>>
>>     coin := sides <~ scale <~ Random new.
>>     flip := Bag <~ count.
>>
>> Coin is an eduction, i.e., it binds transducers to a source and
>> understands #reduce:init: among others.
>> Flip is a transformed reduction, i.e., it binds transducers to a reducing
>> function and an initial value.
>> By sending #<~, we draw further samples from flipping the coin.
>>
>>     samples := flip <~ coin.
>>
>> This yields a new Bag with another 1000 samples.
>>
>>
>>
>> # Basic Concepts
>>
>> ## Reducing Functions
>>
>> A reducing function represents a single step in processing a data
>> sequence.
>> It takes an accumulated result and a value, and returns a new accumulated
>> result.
>> For example:
>>
>>     collect := [:col :e | col add: e; yourself].
>>     sum := #+.
>>
>> A reducing function can also be ternary, i.e., it takes an accumulated
>> result, a key and a value.
>> For example:
>>
>>     collect := [:dic :k :v | dict at: k put: v; yourself].
>>
>> Reducing functions may be equipped with an optional completing action.
>> After finishing processing, it is invoked exactly once, e.g., to free
>> resources.
>>
>>     stream := [:str :e | str nextPut: each; yourself] completing: #close.
>>     absSum := #+ completing: #abs
>>
>> A reducing function can end processing early by signaling Reduced with a
>> result.
>> This mechanism also enables the treatment of infinite sources.
>>
>>     nonNil := [:res :e | e ifNil: [Reduced signalWith: res] ifFalse:
>> [res]].
>>
>> The primary approach to process a data sequence is the reducing protocol
>> with the messages #reduce:init: and #transduce:reduce:init: if transducers
>> are involved.
>> The behavior is similar to #inject:into: but in addition it takes care of:
>> - handling binary and ternary reducing functions,
>> - invoking the completing action after finishing, and
>> - stopping the reduction if Reduced is signaled.
>> The message #transduce:reduce:init: just combines the transformation and
>> the reducing step.
>>
>> However, as reducing functions are step-wise in nature, an application may
>> choose other means to process its data.
>>
>>
>> ## Reducibles
>>
>> A data source is called reducible if it implements the reducing protocol.
>> Default implementations are provided for collections and streams.
>> Additionally, blocks without an argument are reducible, too.
>> This allows to adapt to custom data sources without additional effort.
>> For example:
>>
>>     "XStreams adaptor"
>>     xstream := filename reading.
>>     reducible := [[xstream get] on: Incomplete do: [Reduced signal]].
>>
>>     "natural numbers"
>>     n := 0.
>>     reducible := [n := n+1].
>>
>>
>> ## Transducers
>>
>> A transducer is an object that transforms a reducing function into
>> another.
>> Transducers encapsulate common steps in processing data sequences, such as
>> map, filter, concatenate, and flatten.
>> A transducer transforms a reducing function into another via #transform:
>> in order to add those steps.
>> They can be composed using #* which yields a new transducer that does both
>> transformations.
>> Most transducers require an argument, typically blocks, symbols or
>> numbers:
>>
>>     square := Map function: #squared.
>>     take := Take number: 1000.
>>
>> To facilitate compact notation, the argument types implement corresponding
>> methods:
>>
>>     squareAndTake := #squared map * 1000 take.
>>
>> Transducers requiring no argument are singletons and can be accessed by
>> their class name.
>>
>>     flattenAndDedupe := Flatten * Dedupe.
>>
>>
>>
>> # Advanced Concepts
>>
>> ## Data flows
>>
>> Processing a sequence of data can often be regarded as a data flow.
>> The operator #<~ allows define a flow from a data source through
>> processing steps to a drain.
>> For example:
>>
>>     squares := Set <~ 1000 take <~ #squared map <~ (1 to: 1000).
>>     fileOut writeStream <~ #isSeparator filter <~ fileIn readStream.
>>
>> In both examples #<~ is only used to set up the data flow using reducing
>> functions and transducers.
>> In contrast to streams, transducers are completely independent from input
>> and output sources.
>> Hence, we have a clear separation of reading data, writing data and
>> processing elements.
>> - Sources know how to iterate over data with a reducing function, e.g.,
>> via #reduce:init:.
>> - Drains know how to collect data using a reducing function.
>> - Transducers know how to process single elements.
>>
>>
>> ## Reductions
>>
>> A reduction binds an initial value or a block yielding an initial value to
>> a reducing function.
>> The idea is to define a ready-to-use process that can be applied in
>> different contexts.
>> Reducibles handle reductions via #reduce: and #transduce:reduce:
>> For example:
>>
>>     sum := #+ init: 0.
>>     sum1 := #(1 1 1) reduce: sum.
>>     sum2 := (1 to: 1000) transduce: #odd filter reduce: sum.
>>
>>     asSet := [:set :e | set add: e; yourself] initializer: [Set new].
>>     set1 := #(1 1 1) reduce: asSet.
>>     set2 := #(1 to: 1000) transduce: #odd filter reduce: asSet.
>>
>> By combining a transducer with a reduction, a process can be further
>> modified.
>>
>>     sumOdds := sum <~ #odd filter
>>     setOdds := asSet <~ #odd filter
>>
>>
>> ## Eductions
>>
>> An eduction combines a reducible data sources with a transducer.
>> The idea is to define a transformed (virtual) data source that needs not
>> to be stored in memory.
>>
>>     odds1 := #odd filter <~ #(1 2 3) readStream.
>>     odds2 := #odd filter <~ (1 to 1000).
>>
>> Depending on the underlying source, eductions can be processed once
>> (streams, e.g., odds1) or multiple times (collections, e.g., odds2).
>> Since no intermediate data is stored, transducers actions are lazy, i.e.,
>> they are invoked each time the eduction is processed.
>>
>>
>>
>> # Origins
>>
>> Transducers is based on the same-named Clojure library and its ideas.
>> Please see:
>> http://clojure.org/transducers
>>
>>
>

Reply via email to