我想知道将类似Haskell的数据类型转换为Scheme的最佳方法是什么。我目前的计划是用vector表示构造函数,第一个元素是表示变体的label。例如,下面的Haskell程序:
data Bits = O Bits | I Bits | E deriving Show
data Nat = S Nat | Z deriving Show
inc (O pred) = I pred
inc (I pred) = O (inc pred)
inc E = E
dup (S pred) = let (x,y) = dup pred in (S x, S y)
dup Z = (Z, Z)
bus Z bs = inc bs
bus (S pred) bs = let (x,y) = (pred,pred) in (bus pred (bus pred bs))
o32 = (O (O (O (O (O (O (O (O (O (O (O (O (O (O (O (O (O (O (O (O (O (O (O (O (O (O (O (O (O (O (O (O E))))))))))))))))))))))))))))))))
n26 = (S (S (S (S (S (S (S (S (S (S (S (S (S (S (S (S (S (S (S (S (S (S (S (S (S (S Z))))))))))))))))))))))))))
main = print (bus n26 o32)将被翻译为:
(define (O pred) (vector 'O pred))
(define (I pred) (vector 'I pred))
(define E (vector 'E))
(define (S pred) (vector 'S pred))
(define Z (vector 'Z))
(define (Inc bits) (case (vector-ref bits 0) ('O (I (vector-ref bits 1))) ('I (O (Inc (vector-ref bits 1)))) ('E E)))
(define (Dup val) (case (vector-ref val 0) ('S (let ((xy (Dup (vector-ref val 1)))) (cons (S (car xy)) (S (cdr xy))))) ('Z (cons Z Z))))
(define (Bus n bs) (case (vector-ref n 0) ('Z (Inc bs)) ('S (let ((xy (Dup (vector-ref n 1)))) (Bus (car xy) (Bus (cdr xy) bs))))))
(define O32 (O (O (O (O (O (O (O (O (O (O (O (O (O (O (O (O (O (O (O (O (O (O (O (O (O (O (O (O (O (O (O (O E)))))))))))))))))))))))))))))))))
(define N26 (S (S (S (S (S (S (S (S (S (S (S (S (S (S (S (S (S (S (S (S (S (S (S (S (S (S Z)))))))))))))))))))))))))))
(display (Bus N26 O32))令我惊讶的是,这实际上执行得很好(Scheme在这里比Haskell快)。但我想知道这是不是最好的方法?这是合理的吗,还是有一些更“地道”的翻译,预计会表现得更好?
发布于 2021-04-13 06:49:32
总的来说,我认为您有两种方法:一种是像您在这里所做的那样的“正”编码,其中您有表示包含(动态)标记的产品的向量,以区分sums;另一种是“负”编码(Böhm–Berarducci /访问者),其中您通过使用数据类型的递归方案来表示数据类型。
Haskell中的BB编码版本的一个示例:
{-# Language RankNTypes #-}
-- The type of /folds/ that consume bits.
newtype Bits = Bits
{ matchBits
:: forall r. -- Caller-specified result type.
(r -> r) -- O: 1 recursive field
-> (r -> r) -- I: 1 recursive field
-> r -- E: no fields; could be ‘() -> r’
-> r
}
-- The type of one /recursive unrolling/ of bits.
newtype Bits' = Bits'
{ matchBits'
:: forall r. -- Also any result type.
(Bits -> r) -- But note! ‘Bits’ instead of ‘r’.
-> (Bits -> r)
-> r
-> r
}
-- Basic constructors retain their types.
mkI, mkO :: Bits -> Bits
mkE :: Bits
mkI', mkO' :: Bits' -> Bits'
mkE' :: Bits'
-- Constructor functions perform visitor dispatch.
-- This is where the recursion happens in ‘matchBits’.
mkO pred = Bits $ \ o i e -> o (matchBits pred o i e)
mkI pred = Bits $ \ o i e -> i (matchBits pred o i e)
mkE = Bits $ \ _o _i e -> e
-- General recursive dispatch is similar.
mkO' pred = Bits' $ \ o i e -> o (matchBits' pred mkO mkI mkE)
mkI' pred = Bits' $ \ o i e -> i (matchBits' pred mkO mkI mkE)
mkE' = Bits' $ \ _o _i e -> e
-- Recursive deconstruction, used below.
recurBits :: Bits -> Bits'
recurBits bits = matchBits bits mkO' mkI' mkE'
-- We only need a fold for nats here.
newtype Nat = Nat
{ matchNat
:: forall r. -- Result type.
(r -> r) -- S: 1 recursive field
-> r -- Z: no fields; also could be ‘() -> r’
-> r
}
mkS :: Nat -> Nat
mkZ :: Nat
mkS pred = Nat $ \ s z -> s (matchNat pred s z)
mkZ = Nat $ \ _s z -> z
-- Case branches with ‘matchBits’ receive the /result/
-- of the recursive call on a recursive field. So this
-- is /not/ what we want:
--
-- > inc bits = matchBits bits mkI (mkO . inc) mkE
--
-- Instead, we want the field itself, so we must use
-- the recursive ‘matchBits'’.
inc :: Bits -> Bits
inc bits = matchBits' (recurBits bits) mkI (mkO . inc) mkE
-- Or: ‘dup nat = matchNat nat (mkS *** mkS) (mkZ, mkZ)’
-- Or: ‘dup nat = (nat, nat)’ = ‘dup = join (,)’
dup :: Nat -> (Nat, Nat)
dup nat = matchNat nat
(\ (x, y) -> (mkS x, mkS y)) -- S
(mkZ, mkZ) -- Z
-- NB: think of as ‘Nat -> (Bits -> Bits)’.
bus :: Nat -> Bits -> Bits
bus n = matchNat n
(\ f -> f . f) -- S
inc -- Z您可以将其或多或少直接转换为Scheme。这里是一个未经测试的,可能是不正确的翻译草图,只是为了说明一个起点:
(define (O pred) (lambda (o i e) (o (pred o i e))))
(define (I pred) (lambda (o i e) (i (pred o i e))))
(define E (lambda (o i e) (e)))
(define (O_ pred) (lambda (o i e) (o (pred O I E))))
(define (I_ pred) (lambda (o i e) (i (pred O I E))))
(define E_ (lambda (o i e) (e)))
(define (S pred) (lambda (s z) (s (pred s z))))
(define Z (lambda (s z) (z)))
(define (recurBits bits) (bits O_ I_ E_))
(define (Inc bits)
((recurBits bits)
I
(lambda (pred) (O (Inc pred)))
E))
(define (Dup val)
(val (lambda (p)
(let ((x (car p))
(y (cdr p)))
(cons (S x) (S y))))
(cons Z Z)))
(define (Bus n bs)
((Bus_ n) bs))
(define (Bus_ n)
(n (lambda (pred) (lambda (bs) (pred (pred bs))))
inc))您可能需要在几个地方添加一些显式参数或额外的lambda,以处理部分应用程序和惰性计算中的差异,就像Bus_的笨拙。
不过,总的来说,我希望这种方法在许多应用程序中具有类似或更好的性能特征。它依赖于闭包,而不是向量,闭包很可能编译得更好,因为语言实现更了解它们的结构。它不是动态分派值,而是在函数之间进行选择以(尾部)调用,从而避免构造某些值。
当我在Haskell中学习这项技术时,我也发现Oleg Kiselyov’s notes on BB-encoding是一个很有用的资源。
https://stackoverflow.com/questions/67047759
复制相似问题