2.45

练习 2.45 可以将right-split和up-split表述为某种广义划分操作的实例。请定义一个过程split,使它具有如下性质,求值:

(define right-split (split beside below))
(define up-split (split below beside))

能够产生出过程right-split和up-split,其行为与前面定义的过程一样。


继续沿用 2.45 的代码,先用 JavaScript 探索一下。

Jeff Tian

the Jeff Tian painter

img = document.querySelector('#jeff-tian');

const framedPainter = (frameX, frameY, frameWidth, frameHeight) => (painter) => {
    painter(frameX, frameY, frameWidth, frameHeight);
};

drawToFrame = (frame, width, height) => (framedPainter) => {
    const canvas = document.querySelector(frame);
    canvas.width = width;
    canvas.height = height;

    const ctx = canvas.getContext('2d');
    framedPainter(ctx);
};

const createPainter = (image, sourceX, sourceY, sourceWidth, sourceHeight) => (frameX, frameY, frameWidth, frameHeight) => (ctx) => {
    ctx.drawImage(image, sourceX, sourceY, sourceWidth, sourceHeight, frameX, frameY, frameWidth, frameHeight);
};

// (x, y, w, h) -> ctx -> void
jeffTianPainter = createPainter(img, 0, 0, img.naturalWidth, img.naturalHeight);

onLoadOrLoadAlready = (img, handler) => {
    if(img.complete) {
        handler();
    }

    img.addEventListener('load', handler);
}; 

onLoadOrLoadAlready(img, () => drawToFrame('#my-canvas', img.naturalWidth, img.naturalHeight)(jeffTianPainter(0, 0, img.naturalWidth, img.naturalHeight)));

img.onerror = () => {
    location.reload();
};

beside

// ((x, y, w, h) -> ctx -> void, 
// (x, y, w, h) -> ctx -> void) ->
// (x, y, w, h) -> ctx -> void
beside = (painter1, painter2) => (x, y, w, h) => (ctx) => {
    ctx.save();
    painter1(x, y, Math.floor(w/2), h)(ctx);
    ctx.restore();
    ctx.save();
    painter2(x + Math.floor(w/2) + 1, y, Math.ceil(w/2), h)(ctx);
    ctx.restore();
};

// (x, y, w, h) -> ctx -> void
const besidePainter = beside(jeffTianPainter, jeffTianPainter);

onLoadOrLoadAlready(img, () => drawToFrame('#beside-canvas', img.naturalWidth, img.naturalHeight)(besidePainter(0, 0, img.naturalWidth, img.naturalHeight)));

below

below = (painter1, painter2) => (x, y, w, h) => (ctx) => {
    ctx.save();
    painter1(x, y, w, Math.floor(h/2))(ctx);
    ctx.restore();
    ctx.save();
    painter2(x, y + Math.floor(h/2)+1, w, Math.ceil(h/2))(ctx);
    ctx.restore();
};

const belowPainter = below(jeffTianPainter, jeffTianPainter);

onLoadOrLoadAlready(img, () => drawToFrame('#below-canvas', img.naturalWidth, img.naturalHeight)(belowPainter(0, 0, img.naturalWidth, img.naturalHeight)));

向右分割

split = (op1, op2) => (painter, n) => {
    if (n === 0) {
        return painter;
    }

    const smaller = split(op1, op2)(painter, n-1);

    return op1(painter, op2(smaller, smaller));
}; 

rightSplit = split(beside, below);

const rightSplit1 = rightSplit(jeffTianPainter, 1);

onLoadOrLoadAlready(img, () => drawToFrame('#right-split-1-canvas', img.naturalWidth, img.naturalHeight)(rightSplit1(0, 0, img.naturalWidth, img.naturalHeight)));

向上分割

upSplit = split(below, beside);

const upSplit4 = upSplit(jeffTianPainter, 4);

onLoadOrLoadAlready(img, () => drawToFrame('#up-split-4-canvas', img.naturalWidth, img.naturalHeight)(upSplit4(0, 0, img.naturalWidth, img.naturalHeight)));

写到这里,发现书中的意图是想泛化 split 操作,然后,向上分割与向右分割的区别就仅在于应用 beside 和 below 的顺序不同了。但是我的实现,把向上分割变成了向下分割。如果修复好向上分割,会发现,向右分割又变成了向左分割。

这时才知道,在 2.44 中,我的 below 实现,应该和书中的 below 实现不一样(看到这里,书本还未给出 below 的具体实现)。

于是,我更换了 below 实现中的 painter1 和 painter2 的顺序,即将 painter2 画在上半部分,将 painter1 画在画布的下半部分,这样才在保证向右分割的情况下,修复好了向上分割。

below = (painter1, painter2) => (x, y, w, h) => (ctx) => {
    ctx.save();
    painter2(x, y, w, Math.floor(h/2))(ctx);
    ctx.restore();
    ctx.save();
    painter1(x, y + Math.floor(h/2)+1, w, Math.ceil(h/2))(ctx);
    ctx.restore();
};

upSplit = split(below, beside);

const upSplit1 = upSplit(jeffTianPainter, 1);

onLoadOrLoadAlready(img, () => drawToFrame('#up-split-1-canvas', img.naturalWidth, img.naturalHeight)(upSplit1(0, 0, img.naturalWidth, img.naturalHeight)));

下面只剩下翻译工作了,本质上是把 beside 和 below 抽象成 op1、op2,从而可以动态传入。这当然也就是让原来的函数,再高一阶了。

(define (split op1 op2) 
    (define (op painter n)
        (if (= n 0) painter
                    (
                        (define smaller ((split op1 op2) painter (- n 1)))
                        (op1 painter (op2 smaller smaller))
                    )
    )

    op
)

results matching ""

    No results matching ""