2013-09-04 3 views
8

놀기를 위해 point-free style 자바 스크립트를 가지고 놀아 라. 이 자바 스크립트 기능 렌즈에 대한 자리입니까?

내가 비디오 게임 디아블로를 코딩하고, 나는이 있지만, 더 깊고 더 복잡한처럼 복잡한 중첩 된 유형을 사용하여 원수를 모델링하고 말 :
{ 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 libraries)'의 스타일 (즉, 함수의 함수)로 정확하게하려고합니까? 아니면 일류 게터와 세터를 만들고 있습니까? –

+0

내 값은 더 깊숙이 중첩되어 있지만 그래프가 아닙니다. 깊이 4라고 말하십시오. 기능의 기능이 필요하다고 생각합니다. –

5

귀하의 질문은 자바 스크립트 렌즈를 사용하는 방법에 대해입니다 : 여기에 내 게시물의 코드 단편이다? 그렇다면 도움을 드릴 수 있습니다. Ramda.js library을 확인 했습니까? 그것은 기능적인 JS를 작성하는 훌륭한 방법입니다. 의는 적 모델을보고 시작하자 :

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

렌즈 : 당신이 게터 방법 및 특정 개체에 대한 setter 메소드를 필요로 렌즈를 구성하기 위해를 -은 "적"귀하의 경우. 다음은 직접 작성하는 방법입니다.

방법 1 : 오브젝트

당신이 렌즈를 만든 후에
const healthLens = lensPath(['stats', 'health']); 

에 대한 Ramda의 편법 편리 렌즈는, 그것의 : 자신의 getter 및 setter

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

방법 2를 만들기 그것을 사용할 시간. 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(..).

/* -- 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); 
:

여기
/* -- 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의 모음을 처리 할 때 같이 할 작업은 다음과 같습니다

// 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(..) 
} 

여기에 기능적 접근 방식 : 참고로

, 여기에 원점 FN의

렌즈가 얼마나 유용 할 수 있는지 알려주는 데 도움이되기를 바랍니다.많은 헬퍼 함수가 있지만, 코드의 마지막 부분은 슈퍼 의미론이라고 생각합니다!

마지막으로 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를 지원합니다.