以下是有问题的代码(also on lpaste.net):
module Data.Graph.Dijkstra
( dijkstra
, dijkstraPath
) where
-- Graph library import
import Data.Graph.Inductive hiding (dijkstra)
-- Priority queue import
import qualified Data.PQueue.Prio.Min as PQ
-- Standard imports
import Data.List (find)
import Data.Maybe (fromJust)
import Data.Monoid
-- Internal routine implementing Dijkstra's shortest paths
-- algorithm. Deemed internal because it needs to be kickstarted with
-- a singleton node queue. Based on FGL's current implementation of
-- Dijkstra.
dijkstraInternal ::
(Graph gr, Ord b, Monoid b) => gr a b -> PQ.MinPQueue b [Node] -> [[Node]]
dijkstraInternal g q
| PQ.null q = []
| otherwise =
case match v g of
(Just cxt,g') -> p:dijkstraInternal g' (PQ.unions (q' : expand cxt minDist p))
(Nothing, g') -> dijkstraInternal g' q'
where ((minDist,p@(v:_)), q') = PQ.deleteFindMin q
expand (_,_,_,s) dist pathToC =
map (\(edgeCost,n) -> PQ.singleton (dist `mappend` edgeCost) (n:pathToC)) s
-- Given a graph and a start node, returns a list of lists of nodes
-- corresponding to the shortest paths from the start to all other
-- nodes, where the edge costs are accumulated according to the Monoid
-- instance of the edge label type and costs are compared by the edge
-- label's Ord instance.
dijkstra :: (Graph gr, Ord b, Monoid b) => gr a b -> Node -> [[Node]]
dijkstra g start = dijkstraInternal g (PQ.singleton `mempty` [start]) -- !!!
dijkstraPath :: (Graph gr, Ord b, Monoid b) => gr a b -> Node -> Node -> [LNode a]
dijkstraPath g start goal =
let paths = dijkstra g start
pathNodes = find ((goal ==) . head) paths -- Can paths be empty?
in
case pathNodes of
Nothing -> []
Just ps -> reverse $ map (\n -> (n, fromJust $ lab g n)) ps
奇怪的是第39行,标有-- !!!
评论。此代码编译,但运行时错误是无论如何,PQ.singleton
函数返回一个空的优先级队列。我意识到我不小心向mempty
添加了反引号,所以当我删除那些编译并按预期工作的代码时。
mempty
周围的反引号正确编译代码,这根本不是二进制函数(mempty :: a
)?
在对#haskell提供了一些非常慷慨的帮助之后,我发现它与函数的Monoid实例有关:
instance Monoid b => Monoid (a -> b)
我现在非常模糊地理解为什么这个错误成功地进行了类型检查,但我仍然觉得在某种程度上受到了道德冤枉。有人能解释这是怎么发生的吗?
此外,我还想关注我正在使用的优先级队列的singleton
函数:根据the source,它不会返回空队列。但是,在第24行,相同的优先级队列立即被评估为空。 (我通过跟踪调用验证了这一点。)
答案 0 :(得分:10)
所以,通常,代码:
a `f` b
只是语法糖:
f a b
因此您的代码变为:
mempty PQ.singleton [start]
因此,类型检查器推断出该特定mempty的类型:
mempty :: (k -> a -> PQ.MinPQueue k a) -> [Node] -> PQ.MinPQueue b [Node]
您正确找到了正确的实例。 a -> b
类型的任何内容都是Monoid
,前提是b
。所以我们将上面的类型括起来:
mempty :: (k -> a -> PQ.MinPQueue k a) -> ([Node] -> PQ.MinPQueue b [Node])
因此,如果Monoid
是[Node] -> PQ.MinPQueue b [Node]
,则该类型可以是Monoid
。按照相同的逻辑,如果[Node] -> PQ.MinPQueue b [Node]
是Monoid
,PQ.MinPQueue b [Node]
可以是instance Monoid => Monoid (a -> b) where
mempty = const mempty
。它是什么。因此,使用此代码可以使用类型检查器。
据推测,我们麻烦的实例的实现是:
a -> b
总的来说,你得到一个空的优先级队列。实际上,我认为这归结为一个问题,即设计师是否明智地包含这个实例。它的净效果是返回一个monoid的任何函数都可以是一个monoid,它应该允许你组合结果。这里更有用的例子是mappend,它可以通过应用它们并使用mappend
来组合结果来附加两个extremes = (return . minimum) `mappend` (return . maximum)
函数。例如:
extremes xs = [minimum xs, maximum xs]
而不是:
{{1}}嗯,也许其他人可以产生一个明智的例子。
答案 1 :(得分:5)
所以反引号将二进制函数转换为中缀运算符,使
x `op` y
相当于
op x y
所以op
必须属于a -> b -> c
类型x :: a
和y :: b
。
在您的情况下,op
为mempty
,类型为Monoid m => m
。但是我们知道它的格式为a -> b -> c
,所以替换它,你得到(这不再是有效的语法)Monoid (a -> b -> c) => a -> b -> c
,因为我们可以用m
代替任何东西,只要约束成立。
现在我们知道(由于实例声明)s -> t
形式的任何函数,其中t
是一个Monoid,是一个Monoid本身,我们也知道a -> b -> c
实际上是a -> (b -> c)
,即一个函数接受一个参数并返回另一个函数。因此,如果我们将a
替换为s
而将(b -> c)
替换为t
,则我们将满足Monoid实例,如果t
是Monoid。当然,t
是(b -> c)
,因此我们可以再次应用相同的Monoid实例(使用s = b
和t = c
),因此如果c
是Monoid,我们很好。
那么c
是什么?你的表达是
PQ.singleton `mempty` [start]
即
mempty PQ.singleton [start]
Monoid (a -> b)
的实例声明定义mempty _ = mempty
,即它是一个忽略其参数并返回b
Monoid的空元素的函数。换句话说,我们可以将上面的调用扩展到
mempty [start]
即。我们忽略了这个参数并使用了内部Monoid的mempty(b -> c
)。然后我们重复一遍,再次忽略这个论点:
mempty
所以你所拥有的表达式只相当于一个mempty
,其类型为Monoid c => c
,即它可以是任何Monoid。
在您的情况下,较大的表达式会将c
推断为PQ.MinPQueue
。 MinPQueue
是一个Monoid实例,mempty
是空队列。
这就是你最终得到的结果。
答案 2 :(得分:1)
你已经有了几个很好的答案,我想我会发布这个,因为它有点简单并且帮助了我,因为我在ghci中对此感到困惑。
mempty :: (a -> b) = mempty _ = mempty
所以它基本上是const mempty
。
λ> :t mempty :: (a -> b)
<interactive>:1:1:
No instance for (Monoid b) arising from a use of `mempty'
所以b
必须是Monoid
,因为我们要求该类型的mempty
,这是有道理的。
λ> :t mempty :: (a -> [b])
mempty :: (a -> [b]) :: a -> [b]
λ> :t mempty :: (a -> c -> [b])
mempty :: (a -> c -> [b]) :: a -> c -> [b]
我们可以递归链接这些。由于(->)
是正确的关联(a -> b)
,因此(a -> c -> d)
可能代表b == (c -> d)
。所以我们可以提供任意数量的参数,函数的mempty
将被递归地应用,直到消耗掉所有参数。
λ> import Data.Map
λ> (mempty :: (a -> c -> Map Int Int)) 4 5
fromList []
λ> (mempty :: (a -> c -> d -> Map Int Int)) 4 5 6
fromList []
因此,我们看到应用函数mempty
将丢弃它给出的任何参数,并返回mempty
表达式所在位置的预期类型。