我试图找到一种方法来翻译正常的递归表示法 作为| fib |功能如下箭头,保留尽可能多的 递归表示法的结构尽可能。另外我会的 喜欢检查箭头。为此,我创建了一个包含a的数据类型 每个Arrow {..}类的构造函数:
FIB:
fib 0 = 0
fib 1 = 1
fib n = fib (n-2) + fib (n-1)
My R数据类型,此数据类型的实例由映射组成 到适当的构造函数:
data R x y where
-- Category
Id :: R a a
Comp :: R b c -> R a b -> R a c
-- Arrow
Arr :: (a -> b) -> R a b
Split :: R b c -> R b' c' -> R (b,b') (c,c')
Cache :: (a -> a -> Bool) -> R a a
-- ArrowChoice
Choice :: R b c -> R b' c' -> R (Either b b') (Either c c')
-- ArrowLoop
Loop :: R (b, d) (c, d) -> R b c
-- ArrowApply
Apply :: R (R b c, b) c
翻译| fib |从上面的功能首先导致了 以下定义。但是由于过程不允许这样做 | fibz |声明的RHS。我知道的语法 箭头符号可以防止这种情况,但其根本原因是什么 此?
fib' :: (ArrowChoice r, ArrowLoop r) => r Int Int
fib' = proc x -> do
rec fibz <- proc n -> case n of
0 -> returnA -< 0
1 -> returnA -< 1
n' -> do l <- fibz -< (n'-2)
r <- fibz -< (n'-1)
returnA -< (l+r)
fibz -<< x
重写上面的函数以使用let语句编译。然而, 我的第二个问题出现了。我希望能够检查 它发生的递归。但是,在这种情况下,| fibz |是一个 无限的树。我想捕获到fibz的递归,我 希望rec能帮助我结合| loop |但 也许我错了?
fib'' :: (ArrowChoice r, ArrowLoop r, ArrowApply r) => r Int Int
fib'' = proc x -> do
let fibz = proc n -> case n of
0 -> returnA -< 0
1 -> returnA -< 1
n' -> do l <- fibz -< (n'-2)
r <- fibz -< (n'-1)
returnA -< (l+r)
fibz -<< x
基本上,是否可以观察到这种递归? (也许 即使在Arrow Notation的范围内,我也许可以补充一下 另一个像修复的构造函数。也许我应该能够观察变量的绑定,以便引用它们成为可能。但这不属于箭头的范围。
对此有何想法?
更新1:
我想出了这个形式,在箭头符号之外。这隐藏了app
内部的递归,因此我得到了Arrow的有限表示。但是,我仍然希望能够例如将fib
内的app
来电替换为fib
的优化版本。
fib :: (ArrowChoice r, ArrowLoop r, ArrowApply r) => r Int Int
fib
= (arr
(\ n ->
case n of
0 -> Left ()
1 -> Right (Left ())
n' -> Right (Right n'))
>>>
(arr (\ () -> 0) |||
(arr (\ () -> 1) |||
(arr (\ n' -> (n', n')) >>>
(first ( arr (\ n' -> app (fib, n' - 2))) >>>
arr (\ (l, n') -> (n', l)))
>>>
(first (arr (\ n' -> app (fib, n' - 1))) >>>
arr (\ (r, l) -> (l + r)))))))
此代码对应于箭头符号中的以下内容:
fib :: (ArrowChoice r, ArrowLoop r, ArrowApply r) => r Int Int
fib = proc n ->
case n of
0 -> returnA -< 0
1 -> returnA -< 1
n' ->
do l <- fib -<< (n'-2)
r <- fib -<< (n'-1)
returnA -< (l+r)
答案 0 :(得分:3)
您可以在循环方面编写fib
,例如:
fib'' :: (ArrowChoice r, ArrowLoop r, ArrowApply r) => r Int Int
fib'' = loop $ proc (i, r) -> do
i' <- r -<< i
returnA -< (i', proc j -> case j of
0 -> returnA -< 0
1 -> returnA -< 1
_ -> do
a <- r -< j-2
b <- r -< j-1
returnA -< a + b)
但这实际上只是为一个不需要它的问题引入一个人工循环,并且它在可观察性方面也没有真正为你带来太多。你可以看出存在某种循环,但我认为不可能真正确定递归发生的位置。
在具体表示中,对其他箭头的任何调用都将基本上“内联”,这包括对同一箭头的调用。您无法真正检测到这些呼叫站点,更不用说找出正在调用的箭头。箭头具体化的另一个问题是,许多关于如何传递输入的有趣信息在Arr
黑洞内丢失。
我当然不是箭头方面的专家,我希望有人证明我错了,但我倾向于认为你想要达到的目标是不可能做到的,或者至少是非常不切实际的。我能想到的一个可以帮助您推进的资源是论文Type-Safe Observable Sharing in Haskell和data-reify包。
答案 1 :(得分:0)
您可以使用Category完全重新定义fib,以便您可以定义将代码保存到磁盘并将其加载回来的函数。虽然它有点丑陋。
{-# LANGUAGE GADTs, RankNTypes #-}
module Main where
import Control.Category
data RRef s1 s2 = RRef Int
data R s1 s2 where
Id :: forall s. R s s
Compose :: forall s1 s2 s3. R s2 s3 -> R s1 s2 -> R s1 s3
Lit :: forall s a. a -> R s (a,s)
Dup :: forall s a. R (a,s) (a,(a,s))
Drop :: forall s b. R (b,s) s
Add :: forall s a. Num a => R (a,(a,s)) (a,s)
Decrement :: forall s. R (Int,s) (Int,s)
Deref :: forall s1 s2. RRef s1 s2 -> R s1 s2
Rec :: forall s1 s2. (RRef s1 s2 -> R s1 s2) -> R s1 s2
IsZero :: forall s. R (Int,s) (Bool,s)
If :: forall s1 s2. R s1 s2 -> R s1 s2 -> R (Bool,s1) s2
Swap :: forall s a b. R (a,(b,s)) (b,(a,s))
Over :: forall s a b. R (a,(b,s)) (a,(b,(a,s)))
Rot :: forall s a b c. R (a,(b,(c,s))) (b,(c,(a,s)))
instance Category R where
id = Id
(.) = Compose
fib :: R (Int,()) (Int,())
fib =
Lit 0 >>>
Lit 1 >>>
Rot >>>
Rot >>>
Rec (\ref ->
Dup >>> IsZero >>> (
If
(Drop >>> Swap >>> Drop)
(Decrement >>> Rot >>> Rot >>> Over >>> Add >>> Rot >>> Rot >>> (Deref ref))
)
)
R
这里是一个索引的Monoid,结果与Category
相同。 R
的两个类型参数表示操作之前和之后的堆栈类型签名。堆栈作为程序堆栈,就像汇编代码一样。堆栈类型中的元组形成异构列表以键入堆栈中的每个元素。所有操作(If除外)都采用零参数并只操作堆栈。 If需要两个代码块并返回不带参数的代码,只需操作堆栈。
Rec
用于递归。解释器会为递归函数找到一个唯一的名称(作为整数),然后递归函数将引用该名称Deref
来连接回自身,形成一个递归。
这可以被认为是一种像Forth一样的连接编程语言(如EDSL),除了它对堆栈上的值具有类型安全性。