2013-09-04 63 views
8

Chơi xung quanh với point-free style javascript để giải trí.đây có phải là điểm dành cho ống kính chức năng trong javascript không?

Nói rằng tôi đang mã hóa video game Diablo, và tôi đang mô hình hóa kẻ thù sử dụng các loại lồng nhau phức tạp như thế này nhưng sâu hơn và phức tạp hơn:

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

Vì vậy, tôi có một danh sách của tất cả các kẻ thù của tôi. Tôi muốn làm thiệt hại cho tất cả các kẻ thù trong một bán kính nhất định

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); 

này tất nhiên không gõ kiểm tra - combinators tôi mất nguyên thủy, vì vậy tôi cần phải lập bản đồ và lọc "xuống một cấp độ". Tôi không muốn làm mờ đường dẫn logic của bộ lọc/bản đồ kinh doanh. I know lenses can help me nhưng cho phép nói rằng tôi đang ở trong một trình duyệt, vì điều này là tất nhiên tầm thường với cấu trúc có thể thay đổi. Tôi phải làm nó như thế nào?

Trả lời

6

Đọc my article on lenses. Nó trả lời câu hỏi của bạn chính xác theo cách bạn đã nói nó. Nghiêm túc, tôi thậm chí không nói đùa. Đây là đoạn mã từ bài đăng của tôi:

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

@DustinGetz Vì vậy, bạn có nghĩa là bạn muốn dịch sang Javascript? –

+1

@DustinGetz Bạn đang cố gắng làm điều đó một cách chính xác theo phong cách của thư viện 'thấu kính' (tức là chức năng của các chức năng?) Hay bạn chỉ đang tạo ra các getters và setters hạng nhất? –

+0

Giá trị của tôi được lồng sâu hơn, nhưng không phải là biểu đồ. Nói sâu 4. Tôi nghĩ rằng chức năng của các chức năng là cần thiết. –

5

Câu hỏi của bạn là về cách sử dụng ống kính trong Javascript? Nếu có, tôi có thể giúp. Bạn đã xem qua số Ramda.js library chưa? Đó là một cách tuyệt vời để viết JS chức năng. Hãy bắt đầu bằng cách nhìn vào mô hình kẻ thù của bạn:

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

Lens: Để xây dựng một ống kính bạn cần một phương thức getter và setter một phương pháp cho đối tượng cụ thể của bạn - trong trường hợp của bạn là "kẻ thù". Đây là cách bạn có thể xây dựng chúng bằng tay.

Phương pháp 1: Tạo getter và setter

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

Phương pháp 2 của riêng bạn: thích hợp thuận tiện ống kính Ramda cho đối tượng

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

Khi bạn đã tạo ống kính, đó là thời gian để sử dụng nó. Ramda cung cấp 3 chức năng để sử dụng ống kính: 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 

Vì bạn đang áp dụng fireDamage(..) chức năng đối với sức khỏe của kẻ thù, bạn sẽ muốn sử dụng over(..). Ngoài ra, vì tọa độ vị trí của bạn được lồng trong enemyModel, bạn sẽ muốn sử dụng một ống kính để truy cập chúng. Hãy tạo một và refactor isInRange(..) trong khi chúng ta đang ở đó.

Là một tài liệu tham khảo, đây là fn xứ:

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

Dưới đây là một cách tiếp cận chức năng:

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

Đây là những gì mà sẽ trông như thế khi giao dịch với một bộ sưu tập của 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); 

Tôi hy vọng điều này sẽ giúp minh họa các ống kính hữu ích có thể như thế nào.Trong khi có nhiều chức năng trợ giúp, tôi nghĩ đoạn cuối của mã là siêu ngữ nghĩa!

Cuối cùng, tôi chỉ tràn ngập phạm vi của mình với các chức năng này từ Ramda để làm cho ví dụ này dễ đọc hơn. Tôi đang sử dụng ES6 deconstruction để thực hiện điều này. Dưới đây là cách thực hiện:

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

// code goes below... 

Hãy dùng thử trong jsBin! Họ cung cấp hỗ trợ Ramda.