我想创建一个可以zipWith的列表数据结构,它在自引用方面有更好的表现。这是一个深奥的语言,它将依赖于自引用和懒惰,只使用值(没有用户函数)来完成图灵。我已经创建了它,名为Atlas,但它有很多内置的东西,我想减少它,并能够在Haskell中编译/解释。
问题是zipWith检查列表是否为空并返回空。但是如果这个答案依赖于zipWith的结果,那么它将无限循环。本质上,我希望它检测这种情况并确信列表不会为空。下面是一个使用DList的示例
import Data.DList
import Data.List (uncons)
zipDL :: (a->b->c) -> DList a -> DList b -> DList c
zipDL f a b = fromList $ zipL f (toList a) (toList b)
zipL :: (a->b->c) -> [a] -> [b] -> [c]
zipL _ [] _ = []
zipL _ _ [] = []
zipL f ~(a:as) ~(b:bs) = f a b : zipL f as bs
a = fromList [5,6,7]
main=print $ dh where
d = zipDL (+) a $ snoc (fromList dt) 0
~(Just (dh,dt)) = uncons $ toList d
这段代码将对列表5,6,7求和,但问题是,它可以通过删除zipL _ _ [] = []
来修复,因为它假设结果不是空的,而事实上它也不是空的。但这是一个糟糕的解决方案,因为我们不能总是假设它是第二个列表,它可能有self引用。
另一种解释的方式是,如果我们谈论这些列表的大小。
拉链a的尺寸B = min(尺寸a)(尺寸b)
因此,在本例中:尺寸d =最小值(尺寸a)(尺寸d-1+1)
但问题在于,如果d的大小为0,则d的大小= 0,但如果d的大小为1,则d的大小为1,然而一旦d的大小被说成大于a的大小,则d的大小将为a,这是矛盾的。但任何0-a的大小都有效,这意味着它是未定义的。
本质上我想检测这种情况,并使d = a的大小。
到目前为止,我唯一想到的是把所有的列表都做成Maybe的列表,并用一个Nothing值终止列表。然后在zipWith二进制函数的应用程序中,如果其中一个值是Nothing,则返回Nothing。然后你可以去掉zip中的两个[]检查,因为你可以把所有的列表都看作是无限的。最后,为了使求和的例子有效,不要做snoc,而要做一个map。并将任何Nothing值替换为snoc值。这是因为当检查第二个列表是否为Nothing时,它可以懒洋洋地返回true,因为第二个列表的任何值都不能为Nothing。
代码如下:
import Data.Maybe
data L a = L (Maybe a) (L a)
nil :: L a
nil = L Nothing nil
fromL :: [a] -> L a
fromL [] = nil
fromL (x:xs) = L (Just x) (fromL xs)
binOpMaybe :: (a->b->c) -> Maybe a -> Maybe b -> Maybe c
binOpMaybe f Nothing _ = Nothing
binOpMaybe f _ Nothing = Nothing
binOpMaybe f (Just a) (Just b) = Just (f a b)
zip2W :: (a->b->c) -> L a -> L b -> L c
zip2W f ~(L a as) ~(L b bs) = L (binOpMaybe f a b) (zip2W f as bs)
unconsL :: L a -> (Maybe a, Maybe (L a))
unconsL ~(L a as) = (a, Just as)
mapOr :: a -> L a -> L a
mapOr v ~(L a as) = L (Just $ fromMaybe v a) $ mapOr v as
main=print $ h
where
a = fromL [4,5,6]
b = zip2W (+) a (mapOr 0 (fromJust t))
(h,t) = unconsL $ b
这种方法的缺点是它需要另一个运算符来MapJust . fromMaybe initialvalue
,这是一个不如++
直观的运算符,如果没有它,语言可以完全建立在++
、uncons
和(:[])
上,这将是非常简洁的。
另一件我已经发现的事情是在当前的ruby实现中,当一个值依赖于它自己时抛出一个错误,并在空列表检测中捕获它。但是这是非常笨拙的,而且不完全合理,尽管它确实适用于这样的情况。我认为这在Haskell中不可行,因为我认为你不能检测自依赖?
抱歉,我的描述太长,而且使用案例也很奇怪。我已经花了很多时间思考这个问题,但是还没有解决这个问题,也无法更简洁地解释它!不期待答案,但是我觉得这个问题值得一试,谢谢你的考虑。
编辑:在看到它被框定为最大不动点问题后,它似乎是一个糟糕的问题,因为这样的问题没有有效的通用解决方案。例如,假设代码是b = zipWith (+) a (if length b < 1 then [1] else [])
。
就我的目的而言,正确地处理一些情况仍然是很好的--提供的例子确实有一个解决方案。因此,我可以将问题重新定义为:什么时候我们能有效地找到最大的不动点,那个不动点是什么?2但是我相信对这样的问题没有简单的答案,因此对于一个依赖于特定规则的编程语言来说,这将是一个糟糕的基础。
2条答案
按热度按时间yv5phkfx1#
听起来你想要一个最大的固定点。我不确定我以前是否见过这样做,但是也许可以为支持这些的类型创建一个合理的类型类。
在ghci中尝试一下:
这种实现不是特别有效;例如,将
[5,6,7]
替换为[1..n]
会导致n
中的二次运行时。也许可以通过一些技巧来改进,但对我来说,这并不是立即显而易见的。nfg76nw02#
我有一个针对这个具体案例的答案,而不是笼统的答案。
appendRepeat
将一个无限的重复值列表添加到列表的末尾。但它的关键之处在于,当它决定返回一个非空列表时,如果尾部是递归调用,它不会检查该列表是否为空。这样,懒惰就不会以无限循环检查zipWith _ []
的情况而告终。所以这个代码是有效的,为了回答最初的问题它可以用来将语言转换为只使用2个简单的函数(
++
和:[]
)。但是解释器需要进行一些静态分析,以便使用这个特殊的appendRepeat
函数追加重复值并将其替换为(这可以很容易地在Atlas中完成)。只做这一个实现Switcharoo似乎很笨拙,但这就是所需要的全部。