2013-09-04 5 views
8

遊びのためにpoint-free styleのjavascriptで遊んでください。これはjavascriptの機能的なレンズのためのスポットですか?

は、私はビデオゲームディアブロをコーディングしています、と私はこのような複雑なネストされた型を使用して敵をモデリングが、より深く、より複雑午前言う:

{ name: "badguy1", stats: { health: 10: strength: 42 }, pos: {x: 100, y: 101 } } 

だから私はすべての私の敵のリストを持っています。私のコンビネータは、プリミティブを取るので、私は「別のレベルダウン」マップやフィルタする必要がある - 私は、もちろん、これは型チェックしない

function isInRange(radius, point) { return point.x^2 + point.y^2 >= radius^2; } 
function fireDamage(health) { return health - 10; }  
var newEnemies = enemies.filter(isInRange).map(fireDamage); 

一定の半径内のすべての敵にダメージをやりたいです。私はフィルタ/マップのビジネスロジックのパイプラインをあいまいにしたくありません。 I know lenses can help meしかし、これはもちろん、変更可能な構造では些細なので、私はブラウザにいると言うことができます。どうすればいいのですか?

答えて

6

my article on lensesを読んでください。それはあなたの言葉にちょうどあなたの質問に答えます。真剣に、私は冗談でもない。私のポストのコードスニペットは次のとおりです。

fireBreath :: Point -> StateT Game IO() 
fireBreath target = do 
    lift $ putStrLn "*rawr*" 
    units.traversed.(around target 1.0).health -= 3 
+0

@DustinGetzあなたはそれをJavascriptに翻訳したいとお考えですか? –

+1

@DustinGetz 'lens'ライブラリ(関数の関数)のスタイルで正確にやってみようとしているのですか、あるいはファーストクラスのゲッターとセッターを作成していますか? –

+0

私の値はより深くネストされていますが、グラフはありません。深さ4と言ってください。機能の機能が必要だと思います。 –

5

あなたの質問は、JavaScriptの使用方法についてですか?もしそうなら、私は助けることができるかもしれません。 Ramda.js libraryをチェックしましたか?これは、機能的なJSを書く素晴らしい方法です。のは、あなたの敵モデルを見てみましょう:

/* -- data model -- */ 
let enemyModel = { 
    name: "badguy1", 
    stats: { 
    health: 10, 
    strength: 42 
    }, 
    pos: { 
    x: 100, 
    y: 101 
    } 
}; 

レンズ:あなたはgetterメソッドとあなたの特定のオブジェクトのsetterメソッドを必要とするレンズを構築するためにを - 「敵」あなたのケースで。手でそれらを構築する方法は次のとおりです。

方法1:方法2独自のゲッターとセッター

const getHealth = path(['stats', 'health']); 
const setHealth = assocPath(['stats', 'health']); 
const healthLens = lens(getHealth, setHealth); 

を作成します。 RAMDAの方便利便レンズオブジェクトの

const healthLens = lensPath(['stats', 'health']); 

をあなたはレンズを作成したら、それはですそれを使用する時間。 Ramdaは、レンズを使用するための3つの機能を提供しています:view(..)set(..)、およびover(..)

view(healthLens)(enemyModel); // 10 
set(healthLens, 15)(enemyModel); // changes health from 10 to 15 
over(healthLens, fireDamage)(enemyModel); // reduces enemyModel's health property by 10 

あなたは敵の健康にfireDamage(..)関数を適用しているので、あなたはover(..)を使用したいと思います。また、あなたの位置座標がenemyModel内に入れ子になっているので、レンズを使ってそれらにもアクセスしたいと思うでしょう。私たちがそれをしている間に、一つとリファクタを作りましょう。isInRange(..)参考として

は、ここで原点FNです:

// NOTE: not sure if this works as you intended it to... 

function isInRange(radius, point) { 
    return point.x^2 + point.y^2 >= radius^2; // maybe try Math.pow(..) 
} 

はここで機能的なアプローチです:

/* -- helper functions -- */ 
const square = x => x * x; 
const gteRadSquared = radius => flip(gte)(square(radius)); 
let sumPointSquared = point => converge(
    add, 
    [compose(square, prop('x')), 
    compose(square, prop('y'))] 
)(point); 
sumPointSquared = curry(sumPointSquared); // allows for "partial application" of fn arguments 

/* -- refactored fn -- */ 
let isInRange = (radius, point) => compose(
    gteRadSquared(radius), 
    sumPointSquared 
)(point); 
isInRange = curry(isInRange); 

は、ここでそれはenemyModelsのコレクションを扱うときにどのように見えるかです:

/* -- lenses -- */ 
const xLens = lensPath(['pos', 'x']); 
const yLens = lensPath(['pos', 'y']); 
const ptLens = lens(prop('pos'), assoc('pos')); 

// since idk where 'radius' is coming from I'll hard-code it 
let radius = 12; 

const filterInRange = rad => filter(
    over(ptLens, isInRange(rad)) // using 'ptLens' bc isInRange(..) takes 'radius' and a 'point' 
); 
const mapFireDamage = map(
    over(healthLens, fireDamage) // using 'healthLens' bc fireDamage(..) takes 'health' 
); 

let newEnemies = compose(
    mapFireDamage, 
    filterInRange(radius) 
)(enemies); 

これはレンズの有用性を示すのに役立ちます。ヘルパー関数はたくさんありますが、コードの終わりはスーパーセマンティックです。

最後に、この例をより読みやすくするために、Ramdaのこれらの関数を使用して範囲をフラッディングしています。私はこれを達成するためにES6分解を使用しています。方法は次のとおりです。

const { 
    add, 
    assocPath, 
    compose, 
    converge, 
    curry, 
    filter, 
    flip, 
    gte, 
    lens, 
    lensPath, 
    map, 
    over, 
    set, 
    path, 
    prop, 
    view 
} = R; 

// code goes below... 

jsBinで試してみてください! Ramdaをサポートしています。

関連する問題