/** @module transduce.ts */ import { HKT, Type, URI2HKT, URIS } from 'fp-ts/lib/HKT' import { Applicative, EntryKey, EntryValue, Functor, Predicate, } from './helper-types' import { trace } from './logging' declare module 'fp-ts/lib/HKT' { interface URI2HKT { 'babakness/transduce': Transduce, 'Array': A[], } } const assign = ( prop, value: any, obj: {} ) => Object.assign( {}, obj, {[ prop ]: value} ) const identity = ( a ) => a export const mapPairValue = ( fn: ( a: VA ) => VB ) => ( [ k, v ]: [K, VA] ): [K, VB] => [ k, fn( v ) ] as [K, VB] export const mapPairKey = ( fn: ( a: KA ) => KB ) => ( [ k, v ]: [KA, V] ): [KB, V] => [ fn( k ), v ] as [KB, V] export const filterPairValue = ( fn: Predicate ) => ( [ k, v ]: [K, V] ): boolean => fn( v ) export const filterPairKey = ( fn: Predicate ) => ( [ k, v ]: [K, V] ): boolean => fn( k ) export const mapping = ( transform ) => ( reducing ) => ( acc, item ) => reducing( acc, transform( item ) ) export const filtering = ( predicate: ( item ) => boolean ) => ( reducing ) => ( acc, item ) => predicate( item ) ? reducing( acc, item ) : acc export const filterReducer = ( predicate: ( acc, item ) => boolean ) => ( reducing ) => ( acc, item ) => predicate( acc, item ) ? reducing( acc, item ) : acc export const reduceEntries = ( acc, [ k, v ] ) => assign( k, v, acc ) /** * Idea: implement Either within transduce stream, ie, map become "right" and mapLeft, reduceLeft, etc, operate on Left. Swap could swap left and right */ // export class Transduce { // static readonly _A: A static readonly _URI: 'babakness/transduce' static of( xs: B[] ): Transduce static of( xs: B[] ): Transduce static of( xs: B[] ) { return new Transduce( xs ) } static toPairs( xs: Record ): Transduce static toPairs( xs: Record ): Transduce static toPairs( xs: Record ) { return new Transduce( Object.entries( xs ) ) } private static spreadArgsCompose = ( ...fns ) => ( ...args ) => fns.reduceRight( ( result, fn ) => fn.apply( fn, [].concat( result ) ), args, ) readonly _A!: A readonly _URI!: 'babakness/transduce' // list : A[] constructor( readonly list: A[], readonly xform: ( reducing: any ) => ( acc: any, item: any ) => any = mapping( identity ) ) { // this.list = list // this.xform = xform } map( fn: ( a: A ) => B ): Transduce { const forceB = ( a ): a is B[] => { return true } const list = forceB( this.list ) ? this.list : [] return new Transduce( list, this.compose( mapping( fn ) ) ) } mapPairKey, KB, V extends EntryValue>( fn: ( a: KA ) => KB ): Transduce<[KB, V]> { const forceB = ( a ): a is Array<[KB, V]> => { return true } const list = forceB( this.list ) ? this.list : [] return new Transduce( list, this.compose( mapping( mapPairKey ( fn ) ) ) ) } mapPairValue, VA extends EntryValue, VB>( fn: ( a: VA ) => VB ): Transduce<[K, VB]> { const forceB = ( a ): a is Array<[K, VB]> => { return true } const list = forceB( this.list ) ? this.list : [] return new Transduce( list, this.compose( mapping( mapPairValue ( fn ) ) ) ) } filterType( predicate: ( a: A ) => a is B ): Transduce { const forceB = ( a ): a is B[] => { return true } const list = forceB( this.list ) ? this.list : [] return new Transduce( list, this.compose( filtering( predicate ) ) ) } filter( predicate: ( a: A ) => boolean ): Transduce { return new Transduce( this.list, this.compose( filtering( predicate ) ) ) } filterPairKey>( predicate: ( a: K ) => boolean ): Transduce { // const filterEntryKey = (fn: Predicate) => ([k,v]: [K,V]): boolean => fn(k) return new Transduce( this.list, this.compose( filtering( filterPairKey( predicate ) ) ) ) } filterPairKeyType, V extends EntryValue, KT extends K>( predicate: ( a: K ) => a is KT ): Transduce<[KT, V]> { // const filterEntryKey = (fn: Predicate) => ([k,v]: [K,V]): boolean => fn(k) const forceB = ( a ): a is Array<[KT, V]> => { return true } const list = forceB( this.list ) ? this.list : [] return new Transduce( list, this.compose( filtering( filterPairKey( predicate ) ) ) ) } filterPairValue>( predicate: ( a: V ) => boolean ): Transduce< A> { return new Transduce( this.list, this.compose( filtering( filterPairValue( predicate ) ) ) ) } filterPairValueType, V extends EntryValue, VT extends V>( predicate: ( a: V ) => a is VT ): Transduce<[K, VT]> { const forceB = ( a ): a is Array<[K, VT]> => { return true } const list = forceB( this.list ) ? this.list : [] return new Transduce( list, this.compose( filtering( filterPairValue( predicate ) ) ) ) } chain( fn: ( a: A ) => Transduce ): Transduce < B[] > { const forceB = ( a ): a is B[][] => { return true } const list = forceB( this.list ) ? this.list : [] // return new Transduce(this.join().map( i => fn(i).join() ), mapping(identity) ) return new Transduce( list, this.compose( ( reducing ) => ( acc, item ) => reducing( acc, fn( item ).join() ) ) ) } ap( other: Transduce< ( a: A ) => B > ): Transduce < B > { const foo = other.map( ( f ) => this.map( ( x ) => f( x ) ).join() as any ) as Transduce return foo } take( n: number ): Transduce < A > { return new Transduce( this.list, this.compose( filterReducer( ( acc, item ) => acc.length + 1 <= n ? true : false ) ) ) } every( n: number , offset = 0 ): Transduce < A > { let count = -1 const reducer = ( acc, item ) => count++ < offset - 1 ? false // ( trace( count ), false ) : ( count + offset ) % n === 0 ? true : false // const reducer = getReducer( 0 ) return new Transduce( this.list, this.compose( filterReducer( reducer ) ) ) } /** * order switched for compatibility with fp-ts library */ fold( init: B, concat: ( acc: B, item: A ) => B ): B { return this.list.reduce( this.xform( concat ), init ) } reduce( concat: ( acc: B, item: A ) => B, init: B ): B { return this.list.reduce( this.xform( concat ), init ) } join( concat = ( acc, item ) => acc.concat( item ), init = [] ): A[ ] { return this.list.reduce( this.xform( concat ), init ) } /** * If the inner value is of form [K,V][] (see this `param` for details), * then this method will reduce or fold the inner value into an object. */ joinFromPairs < K, B extends A extends [K, ( infer U )] ? A : undefined >( this: Transduce < B > , init = {} ): { [k: string]: A extends [K, ( infer U )] ? U : A } { const concat = ( acc, [ k, v ] ) => assign( k, v, acc ) return this.list.reduce( this.xform( concat ), init ) } traverseLegacy( this: Transduce < HKT < F, B >> , of: ( a: Transduce ) => Type < F, Transduce < B >> , fn: ( a: B ) => C ): Type < F, Transduce < C >> { const ofArray = this.list.reduce( this.xform( ( outer, item ) => outer.ap( item.map( ( itemValue ) => ( outerValue ) => outerValue.concat( fn( itemValue ) ) ) ), ), of( [] as any ) ) return ofArray.map( ( arr ) => Transduce.of( arr ) ) } traverse( F: Applicative < B | never[ ] | B[ ] | C > , fn: ( a: A ) => HKT & Functor ): Type < F, Transduce < B >> { const ofArray = this.list.reduce( this.xform( ( outer, item ) => outer.ap( fn( item ).map( ( itemValue ) => ( outerValue ) => outerValue.concat( itemValue ) ) ), ), F.of( [] ) ) return ofArray.map( ( arr ) => Transduce.of( arr ) ) } sequenceLegacy( this: Transduce < HKT < F, B >> , of: ( a: Transduce ) => Type> ): Type < F, Transduce < B >> { return this.traverseLegacy( of, identity ) } sequence( this: Transduce < HKT < F, B >> , F: Applicative | never[] | B[] > ): Type < F, Transduce < B >> { return this.traverseLegacy( F.of as any, identity ) } traverse_( this: Transduce < HKT < F, B >> , of: ( a: Transduce ) => Type < F, Transduce < B >> , fn: ( a: Type ) => HKT ): Type < F, Transduce < C >> { const ofArray = this.list.reduce( this.xform( ( outer, item ) => outer.ap( ( fn( item ) as any ).map( ( itemValue ) => ( outerValue ) => outerValue.concat( itemValue ) ) ), ), of( [] as any ) ) return ofArray.map( ( arr ) => Transduce.of( arr ) ) } sequenceNonStandardFunctor( this: Transduce < HKT < F, B >> , of: ( a: Transduce ) => Type> ): Type < F, Transduce < B >> { // hacky solution when only `map` is known to behave as expected (non-Fantasy `ap`) const _arr = this.list.reduce( this.xform( ( arr, functor ) => { functor.map( ( value ) => arr.push( value ) ) return arr } ), [] ) return of( Transduce.of( _arr ) ) } private compose( fn ) { return Transduce.spreadArgsCompose( this.xform, fn ) } }