(define conway-kernel
(<< (Array #(1 1 1 1 0 1 1 1 1))
(rho2 3 3)))
(define (neighbor grid)
(<< conway-kernel
(Convolve2 grid '(1 1))))
(define (step grid)
(Materialize2
((Zip-with
(λ (x n)
(if (= x 1)
(if (or (= n 2) (= n 3)) 1 0)
(if (= n 3) 1 0))))
grid (neighbor grid))))
生命游戏可以简单地如此定义. (注意到我修改了一些标识符的名字, 然而和原本基本上是可以对应起来的.)
当然, 这牵涉一个神秘的过程Convolve2. 如名字所暗示的那样, 其是二维卷积.
(define (Convolve2 A center)
(>> (Mapi
(λ (n . indices)
(if (zero? n)
(Zero A)
(<< A (Map (curry * n))
(Shift (list- center indices))))))
(Reduce2 (Zip-with +))))
实际上, 如果使用可以应用于任意维度的通用Reduce, 那么Convolve2的这个定义本质上也可以直接应用于任意的维度, 或者说就变成了通用的卷积. 这个定义实际上是很有趣的, 因为它不是逐个元素进行计算, 而是将卷积核里的那些元素直接乘上要被变换的矩阵, 然后进行某个偏移, 最后把这些矩阵加起来. 所以说, 这个定义才可以直接推广.
这个定义牵涉一些辅助函数.
(define (Zero A)
(match A
((array shape _)
(array shape (const 0)))))
(define (list- l1 l2)
(map - l1 l2))
(define (restrict x low high)
(min (max low x) high))
(define (clamp ref shape)
(map (lambda (x i)
(restrict x 0 (- i 1)))
ref shape))
(define (valid? indices shape)
(andmap
(λ (x i) (<= 0 x (- i 1)))
indices shape))
(define (strategy:set0 shape indexf)
(λ indices
(if (valid? indices shape)
(apply indexf indices)
0)))
(define (strategy:clamp shape indexf)
(λ indices
(apply indexf (clamp indices shape))))
(define strategy:current
(make-parameter strategy:set0))
(define ((Shift offset) A)
(match A
((array shape indexf)
(array shape
(λ indices
(apply
((strategy:current) shape indexf)
(list- indices offset)))))))
其中参数strategy:current定义了处理边界条件的策略, 这里我们是对于越界访问置零.
有些辅助定义是不必要的, 然而之后的例子会用到. 之后, 我们将会描述plasma fractals (等离子体分形).