2

タスクにはいくつかのステップがあります。各ステップの入力が直接最後のステップからのものであれば簡単です。しかし、より多くの場合、いくつかのステップは直接最後のステップに依存するだけではありません。folktale2で関数型プログラミングのjavascriptを使用すると、前のタスクの結果に正常にアクセスする方法は?

私はいくつかの方法で解決することができますが、すべてが醜いネストされたコードで終わってしまいます。誰も私がより良い方法を見つけるのを助けてくれることを願っています。 ( - >タスク接続())

  • 検索アカウント(接続 - >タスク

    1. GETデータベース接続:

      私は証明するために、次のサインインのような例を作成し、プロセスは以下のように3つのステップがありますアカウント)

    2. トークン(接続の作成] - > [アカウントID] - > [タスクトークン)

    を#のSTEP3は、ステップ#2にばかり依存でなく、#1にステップ。

    以下は試み#1が右ですが、私は手順がそれを必要とするまで、それならば、非常に早い段階の出力を渡す必要がfolktale2

    import {task, of} from 'folktale/concurrency/task' 
    import {converge} from 'ramda' 
    
    const getDbConnection =() => 
        task(({resolve}) => resolve({id: `connection${Math.floor(Math.random()* 100)}`}) 
    ) 
    
    const findOneAccount = connection => 
        task(({resolve}) => resolve({name:"ron", id: `account-${connection.id}`})) 
    
    const createToken = connection => accountId => 
        task(({resolve}) => resolve({accountId, id: `token-${connection.id}-${accountId}`})) 
    
    const liftA2 = f => (x, y) => x.map(f).ap(y) 
    
    test('attempt#1 pass the output one by one till the step needs: too many passing around', async() => { 
        const result = await getDbConnection() 
         .chain(conn => findOneAccount(conn).map(account => [conn, account.id])) // pass the connection to next step 
         .chain(([conn, userId]) => createToken(conn)(userId)) 
         .map(x=>x.id) 
         .run() 
         .promise() 
    
        console.log(result) // token-connection90-account-connection90 
    }) 
    
    test('attempt#2 use ramda converge and liftA2: nested ugly', async() => { 
        const result = await getDbConnection() 
         .chain(converge(
          liftA2(createToken), 
          [ 
           of, 
           conn => findOneAccount(conn).map(x=>x.id) 
          ] 
         )) 
         .chain(x=>x) 
         .map(x=>x.id) 
         .run() 
         .promise() 
    
        console.log(result) // token-connection59-account-connection59 
    }) 
    
    test('attempt#3 extract shared steps: wrong', async() => { 
        const connection = getDbConnection() 
    
        const accountId = connection 
        .chain(conn => findOneAccount(conn)) 
        .map(result => result.id) 
    
        const result = await of(createToken) 
        .ap(connection) 
        .ap(accountId) 
        .chain(x=>x) 
        .map(x=>x.id) 
        .run() 
        .promise() 
    
        console.log(result) // token-connection53-account-connection34, wrong: get connection twice 
    }) 
    
    • を使用して、冗談ユニットテストですそれは非常に面倒です。

    • 試行#2も正しいですが、ネストされたコードで終わります。

    • 私は試行#3が好きですが、変数を使用して値を保持していますが、残念ながら動作しません。

    更新-1 私は通過する状態にすべての出力を配置する別の方法を考えていますが、それかもしれない非常によく似た試み#1

    test.only('attempt#4 put all outputs into a state which will pass through', async() => { 
        const result = await getDbConnection() 
        .map(x=>({connection: x})) 
        .map(({connection}) => ({ 
         connection, 
         account: findOneAccount(connection) 
        })) 
        .chain(({account, connection})=> 
         account.map(x=>x.id) 
         .chain(createToken(connection)) 
        ) 
        .map(x=>x.id) 
        .run() 
        .promise() 
    
    
        console.log(result) //  token-connection75-account-connection75 
    }) 
    

    更新-2 @ Scottのdoアプローチを使用することで、私は以下のアプローチにかなり満足しています。それは短く清潔です。これはあなたの第二の試みと似て

    const withConnection = connection => 
        findOneAccount(connection) 
         .map(x => x.id) 
         .chain(createToken(connection)) 
    
    getDbConnection().chain(withConnection) 
    

    しかし、その後のchain(identity)の必要性を取り除くためにchainではなくap/liftを利用し、次のように

    test.only('attempt#5 use do co', async() => { 
        const mdo = require('fantasy-do') 
    
        const app = mdo(function *() { 
         const connection = yield getDbConnection() 
         const account = yield findOneAccount(connection) 
    
         return createToken(connection)(account.id).map(x=>x.id) 
        }) 
    
        const result = await app.run().promise() 
    
        console.log(result) 
    }) 
    
  • +1

    私は以前の 'タスク'の結果にアクセスする方法が正しいと思いますか?これを明示的にステートオブジェクトをチェーンに渡すことでこれを行うことはそれほど悪くはありません。さらに、 'task'と' readT'モナド・トランスフォーマー(または、あなたが変異が必要な場合は 'stateT')を組み合わせて、そのオブジェクトから抽象化することができます。しかし、私はこの抽象化が努力する価値があるのか​​どうか、Javacriptのプロトタイプシステムで適切な 'readerT'を実装できるかどうかはわかりません。 – ftor

    +0

    @ftor、私は 'readerT'モナドを理解するためにいくつかのhaskellを学ぶ必要があると思います。私は 'readerT'と' stateT'を調べます。この情報に感謝します。 – Ron

    +0

    関連する、[以前の約束の結果を.then()チェーンにどのようにしてアクセスするのですか?](https://stackoverflow.com/q/28250680/1048572) - 「タスク」は熱心ではないが、モナドインターフェースと 'parallel'の組み合わせは、約束と全く同じです。 – Bergi

    答えて

    2

    あなたの例では、書くことができます。これはまた、必要に応じてconvergeを使用するように更新することもできますが、プロセスでは読みやすさが失われると感じています。

    const withConnection = R.converge(R.chain, [ 
        createToken, 
        R.compose(R.map(R.prop('id')), findOneAccount) 
    ]) 
    
    getDbConnection().chain(withConnection) 
    

    ジェネレータを使用して3回目の試みと同様に見えるように更新することもできます。 Do関数の以下の定義は、何らかの形式の「構文」を提供する既存のライブラリの1つに置き換えることができます。

    // sequentially calls each iteration of the generator with `chain` 
    const Do = genFunc => { 
        const generator = genFunc() 
        const cont = arg => { 
        const {done, value} = generator.next(arg) 
        return done ? value : value.chain(cont) 
        } 
        return cont() 
    } 
    
    Do(function*() { 
        const connection = yield getDbConnection() 
        const account = yield findOneAccount(connection) 
        return createToken(connection)(account.id) 
    }) 
    
    +0

    あなたのご意見ありがとうございます。最初のコードセットはきれいに見えますが、ネストされているので、2番目のコードセットはあまり読みにくくないことに同意しました。最後はうまく聞こえる、「約束」の世界では「共」が好きだ、面白い、そういうものは存在しない。 – Ron

    +1

    @Ronネストされたコンティニュイションを独自の 'withConnection'関数に引き出す例を編集しましたが、それは表面的にのみネストを減らします。私はあなたにも 'Do'の単純な実装を含んでいますが、すでにこの機能を提供している既存のライブラリもいくつかありますが、https://github.com/jwoudenberg/fantasy-doやhttps://github.comのように/ pelotom/burrido –

    +0

    最新情報をお寄せいただきありがとうございます。私はまだ更新されたコードセットがネストされていると感じていますが、私はあなたの 'do'アプローチが2つのライブラリで好きです。 – Ron

    関連する問題