edit: 请参阅这个后续问题简化了我试图在此处识别的问题,并要求对GHC修改建议。
所以我试图写一个通用的广度优先搜索功能,并提出以下内容:
bfs :: (a -> Bool) -> (a -> [a]) -> [a] -> Maybe a
bfs predf expandf xs = find predf bfsList
where bfsList = xs ++ concatMap expandf bfsList
我认为非常优雅,但是在不存在的情况下,它永远阻止了。
在将所有术语扩展到[]
之后,concatMap
将永远不会返回另一个项目,因此concatMap
正在阻止等待其本身的其他项目?可以使Haskell足够聪明,以意识到列表的一代已被阻止阅读自我参考并终止列表吗?
我能想到的最好的替代品并不那么优雅,因为我必须自己处理终止案例:
where bfsList = concat.takeWhile (not.null) $ iterate (concatMap expandf) xs
对于具体的示例,第一个搜索终止了成功,第二个搜索是:
:bfs (==3) (x -> if x<1 then [] else [x/2, x/5]) [5, 3*2**8]
bfs (==3) (x -> if x<1 then [] else [x/2, x/5]) [5, 2**8]
编辑添加注释以解释我的bfs'
解决方案。
您的问题的措辞("可以使Haskell变得足够聪明"),听起来您认为计算的正确值:
bfs (x -> False) (x -> []) []
鉴于您对bfs
的原始定义应为Nothing
,Haskell只是找不到正确的答案。
但是,上述计算的正确值是底部的。替换bfs
的定义(并简化[] ++
表达式),上述计算等于:
find (x -> False) bfsList
where bfsList = concatMap (x -> []) bfsList
评估find
要求确定bfsList
是否为空,因此必须将其强加于弱头正常形式。这种强迫需要评估concatMap
表达式,必须确定bfsList
是否为空,将其迫使其迫使WHNF。此强制循环意味着bfsList
是底部的,因此find
也是如此。
haskell 在检测循环并给出错误时可能会更聪明,但是返回[]
是不正确的。
最终,这与以下方式发生了同一件事:
foo = case foo of [] -> []
也无限地循环。Haskell的语义暗示该case
构造必须强制foo
,并且强迫foo
需要强迫foo
,因此结果是最底层的。的确,如果我们将此定义视为方程式,那么替换foo = []
就会"满足"它,但这不是Haskell语义的工作方式,其原因是:
bar = bar
没有值1
或"awesome"
,即使这些值以"方程式"为准。
所以,您问题的答案是,不,无法更改此行为,以便在没有从根本上更改Haskell语义的情况下返回空列表。
另外,作为看起来很光滑的替代方案 - 即使有明确的终止条件,也许会考虑:
bfs' :: (a -> Bool) -> (a -> [a]) -> [a] -> Maybe a
bfs' predf expandf = look
where look [] = Nothing
look xs = find predf xs <|> look (concatMap expandf xs)
这使用Maybe
的Alternative
实例,这确实很简单:
Just x <|> ... -- yields `Just x`
Nothing <|> Just y -- yields `Just y`
Nothing <|> Nothing -- yields `Nothing` (doesn't happen above)
因此,look
使用find
检查当前值xs
,如果失败并返回Nothing
,它会递归地查看其扩展。
作为使终止条件看起来不太明确的愚蠢示例,这是使用listToMaybe
作为终结器的双蒙德(也许是隐式读者)版本!(不推荐在实际代码中。)
bfs'' :: (a -> Bool) -> (a -> [a]) -> [a] -> Maybe a
bfs'' predf expandf = look
where look = listToMaybe *>* find predf *|* (look . concatMap expandf)
(*>*) = liftM2 (>>)
(*|*) = liftM2 (<|>)
infixl 1 *>*
infixl 3 *|*
这是如何工作的?好吧,这是个玩笑。作为提示,look
的定义与:
where look xs = listToMaybe xs >>
(find predf xs <|> look (concatMap expandf xs))
我们以步骤生成结果列表(队列)。在每个步骤中,我们消耗了上一步上生产的内容。当最后一个扩展步骤什么也没添加时,我们停止:
bfs :: (a -> Bool) -> (a -> [a]) -> [a] -> Maybe a
bfs predf expandf xs = find predf queue
where
queue = xs ++ gen (length xs) queue -- start the queue with `xs`, and
gen 0 _ = [] -- when nothing in queue, stop;
gen n q = let next = concatMap expandf (take n q) -- take n elemts from queue,
in next ++ -- process, enqueue the results,
gen (length next) (drop n q) -- advance by `n` and continue
因此我们得到
~> bfs (==3) (x -> if x<1 then [] else [x/2, x/5]) [5, 3*2**8]
Just 3.0
~> bfs (==3) (x -> if x<1 then [] else [x/2, x/5]) [5, 2**8]
Nothing
该解决方案中的一个潜在严重流程是,如果任何expandf
步骤产生无限的结果列表,它将被卡住计算其length
,完全不必要。
通常,只需引入一个计数器,然后通过在每个扩展步骤(length . concatMap expandf
或某些东西)下产生的解决方案的长度来递增,以减少所消耗的数量。当它到达0
时,请勿尝试使用任何东西,因为那时没有什么可消费的,您应该终止。
该计数器实际上是指向构造队列的指针。 n
的值表示将放置下一个结果的位置是 n
notches,距离获取输入的列表中的位置。因此,1
意味着下一个结果直接放置在输入值之后。
可以在Wikipedia的有关Corecursion的文章中找到以下代码(搜索" Corecursive deqeue"):
data Tree a b = Leaf a | Branch b (Tree a b) (Tree a b)
bftrav :: Tree a b -> [Tree a b]
bftrav tree = queue
where
queue = tree : gen 1 queue -- have one value in queue from the start
gen 0 _ = []
gen len (Leaf _ : s) = gen (len-1) s -- consumed one, produced none
gen len (Branch _ l r : s) = l : r : gen (len+1) s -- consumed one, produced two
这种技术在序言中是自然的,具有自上而下的列表实例化和逻辑变量,可以在 dot> dot-net-set状态中明确。另请参见tailRecursion-modulo-cons。
bfs
中的 gen
可以重新编写为更加增量,这通常是一件好事:
gen 0 _ = []
gen n (y:ys) = let next = expandf y
in next ++ gen (n - 1 + length next) ys
bfsList
是递归定义的,这本身并不是Haskell中的问题。但是,它确实产生了一个无限的列表,这本身并不是一个问题,因为Haskell被懒惰地评估。
只要find
最终找到了它想要的东西,这并不是一个无限元素的问题,因为当时评估停止(或者,更重要的是要去做其他事情)。
afaict,第二种情况下的问题是谓词永远不匹配,因此bfsList
只是继续生产新元素,而find
不断寻找。
在所有条款都扩展到[] concatmap之后,永远不会返回另一个项目
您确定这是正确的诊断吗?据我所知,在上面提供的lambda表达式的情况下,每个输入元素总是扩展到两个新元素 - 永远不要 []
。但是,该列表是无限的,因此,如果谓词无与伦比,则该功能将永远评估。
可以使Haskell变得足够聪明,以意识到列表的一代已被阻止阅读自我参考并终止列表?
如果有通用算法来确定计算是否最终将完成,那将是不错的。las,正如图灵和教堂(彼此独立于)在1936年证明的那样,这种算法不可能存在。这也称为停止问题。不过,我不是数学家,所以我可能错了,但是我认为也适用于这里...
我能想到的最好的替代品并不那么优雅
不确定那个...如果我尝试使用它而不是bfsList
的另一个定义,它不会编译...尽管如此,我不认为问题是空列表。