/** @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 )
}
}