機能設計:「時間」コンビナータをより一般的にする方法


私が書いたのはtime 類似のUNIXコマンドを模倣するコンバイナレータIO<A> , 我々は行動を得ることができるIO<A> これは、経過時間
import { IO, io } from 'fp-ts/IO'
import { now } from 'fp-ts/Date'
import { log } from 'fp-ts/Console'

export function time<A>(ma: IO<A>): IO<A> {
  return io.chain(now, start =>
    io.chain(ma, a => io.chain(now, end => io.map(log(`Elapsed: ${end - start}`), () => a)))
  )
}
この組み合わせには2つの問題があります.
  • すなわち、消費者は経過時間と何をすべきかを選択できない
  • 作品IO のみ
  • この記事では、最初の問題に取り組む.

    経過時間を返すことによる柔軟性の追加
    常にログの代わりに、計算された値と一緒に経過時間を返すことができます
    export function time<A>(ma: IO<A>): IO<[A, number]> {
      return io.chain(now, start => io.chain(ma, a => io.map(now, end => [a, end - start])))
    }
    
    今、ユーザーは、それ自身の組み合わせを定義することによって経過時間と何をすべきかを選択することができます.
    我々はまだコンソールにログオンすることができます.
    export function withLogging<A>(ma: IO<A>): IO<A> {
      return io.chain(time(ma), ([a, millis]) =>
        io.map(log(`Result: ${a}, Elapsed: ${millis}`), () => a)
      )
    }
    
    用途
    import { randomInt } from 'fp-ts/Random'
    
    function fib(n: number): number {
      return n <= 1 ? 1 : fib(n - 1) + fib(n - 2)
    }
    
    const program = withLogging(io.map(randomInt(30, 35), fib))
    
    program()
    /*
    Result: 14930352, Elapsed: 127
    */
    
    ...またはちょうど経過時間を無視する.
    export function ignoreSnd<A>(ma: IO<[A, unknown]>): IO<A> {
      return io.map(ma, ([a]) => a)
    }
    
    ...または、例えば、アクションの非空のリストの最速を維持する
    import { fold, getMeetSemigroup } from 'fp-ts/Semigroup'
    import { contramap, ordNumber } from 'fp-ts/Ord'
    import { getSemigroup } from 'fp-ts/IO'
    
    export function fastest<A>(head: IO<A>, tail: Array<IO<A>>): IO<A> {
      const ordTuple = contramap(([_, elapsed]: [A, number]) => elapsed)(ordNumber)
      const semigroupTuple = getMeetSemigroup(ordTuple)
      const semigroupIO = getSemigroup(semigroupTuple)
      const fastest = fold(semigroupIO)(time(head), tail.map(time))
      return ignoreSnd(fastest)
    }
    
    用途
    io.chain(fastest(program, [program, program]), a => log(`Fastest result is: ${a}`))()
    /*
    Result: 5702887, Elapsed: 49
    Result: 2178309, Elapsed: 20
    Result: 5702887, Elapsed: 57
    Fastest result is: 2178309
    */
    
    我々はプログラミングの強力なスタイルを導入することによって、第2の問題に取り組むでしょう:タグのない決勝.

    付録
    の実装fastest かなり密です.関連するビットを見てみましょう.
    1 )署名は、空のアクションリストを提供します
    //  at least one action --v            v--- possibly other actions
    function fastest<A>(head: IO<A>, tail: Array<IO<A>>): IO<A>
    
    2).contramapOrd の組み合わせを指定しますOrd for T からの関数U to T , のインスタンスを得ることができますOrd for U .
    ヒアT = number and U = [A, number]
    // from `Ord<number>` to `Ord<[A, number]>`
    const ordTuple = contramap(([_, elapsed]: [A, number]) => elapsed)(ordNumber)
    
    3).getMeetSemigroup インスタンスを変換するOrd<T> のインスタンスにSemigroup<T> これは、2つの値を組み合わせた場合、
    // from `Ord<[A, number]>` to `Semigroup<[A, number]>`
    const semigroupTuple = getMeetSemigroup(ordTuple)
    
    4).getSemigroupSemigroup の組み合わせを指定しますSemigroup for T , のインスタンスを得ることができますSemigroup for IO<T>
    // from `Semigroup<[A, number]>` to `Semigroup<IO<[A, number]>>`
    const semigroupIO = getSemigroup(semigroupTuple)
    
    5)fold 指定したアクションの空のリストを減らすSemigroup
    // from a non empty list of `IO<[A, number]>` to `IO<[A, number]>`
    const fastest = fold(semigroupIO)(time(head), tail.map(time))
    
    6 )最終的に経過時間を無視して値を返す
    // from `IO<[A, number]>` to `IO<A>`
    return ignoreSnd(fastest)