数组语言的用例1: 生命游戏

(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 (等离子体分形).

你的回應