Haskell Continuation传递列表中元素的样式索引
为了练习Haskell,我尝试了一系列的例子。我目前正在学习continuation passing,但对于如何实现像find index of element in list这样的函数,我有点困惑,它的工作原理如下:Haskell Continuation传递列表中元素的样式索引,haskell,functional-programming,continuation-passing,Haskell,Functional Programming,Continuation Passing,为了练习Haskell,我尝试了一系列的例子。我目前正在学习continuation passing,但对于如何实现像find index of element in list这样的函数,我有点困惑,它的工作原理如下: index 3 [1,2,3] id = 2 像阶乘这样的例子是有意义的,因为除了乘法之外,实际上没有任何数据处理,但是在索引函数的情况下,我需要比较我正在寻找的元素和我正在寻找的元素,而我似乎不知道如何使用函数参数来做 任何帮助都会很好。首先让我向您展示一个可能的实现: in
index 3 [1,2,3] id = 2
像阶乘这样的例子是有意义的,因为除了乘法之外,实际上没有任何数据处理,但是在索引函数的情况下,我需要比较我正在寻找的元素和我正在寻找的元素,而我似乎不知道如何使用函数参数来做
任何帮助都会很好。首先让我向您展示一个可能的实现:
index :: Eq a => a -> [a] -> (Int -> Int) -> Int
index _ [] _ = error "not found"
index x (x':xs) cont
| x == x' = cont 0
| otherwise = index x xs (\ind -> cont $ ind + 1)
如果您更喜欢无点样式:
index :: Eq a => a -> [a] -> (Int -> Int) -> Int
index _ [] _ = error "not found"
index x (x':xs) cont
| x == x' = cont 0
| otherwise = index x xs (cont . (+1))
工作原理
诀窍是使用continuations对索引进行计数-这些continuations将使索引向右移动,并将其递增
如您所见,如果找不到元素,这将导致错误
示例:
我怎么知道的
解决这类问题的一个好方法是首先用continuation写下递归调用:
useCont a (x:xs) cont = useCont a xs (\valFromXs -> cont $ ??)
现在,您必须考虑您想要的valFromXs
是什么(作为一个类型和一个值)-但是请记住,您典型的开始(如下所示)将是进行第一个continuationid
,因此类型只能是Int->Int
。所以很明显,我们在这里讨论的是索引转换的概念。由于useCont
在下一次调用中将只知道尾部xs
,因此很自然地将此索引视为相对于xs
的,从这里开始,其他索引应该很快跟进
依我看,这只是另一个例子
让类型引导你
)()
评论
我不认为这是Haskell中延续的典型用法
这一次,您也可以使用一个累加器参数来实现这一点(这在概念上更简单):
或者()您可以使用Haskell的惰性/列表理解来让它看起来更漂亮:
index :: Eq a => a -> [a] -> Int
index x xs = head [ i | (x',i) <- zip xs [0..], x'== x ]
index::Eq a=>a->[a]->Int
索引xxs=head[i |(x',i)如果您有一个值a
,那么要将其转换为CPS样式,您可以将其替换为(a->r)->r
之类的内容,用于一些未指定的r
。在您的例子中,基本函数是index::Eq a=>a->[a]>可能是Int
,因此CPS形式是
index :: Eq a => a -> [a] -> (Maybe Int -> r) -> r
甚至
index :: Eq a => a -> [a] -> (Int -> r) -> r -> r
让我们实现后者
index x as success failure =
值得注意的是,有两种延续,一种是成功的结果,另一种是失败的结果。我们将根据需要应用它们,并像往常一样导入列表的结构。首先,很明显,如果as
列表为空,那么这就是失败
case as of
[] -> failure
(a:as') -> ...
在成功案例中,我们通常对x==a
感兴趣。如果是真的,我们将成功延续传递给索引0
,因为毕竟,我们在输入列表的0
第个索引中找到了匹配项
case as of
...
(a:as') | x == a -> success 0
| otherwise -> ...
那么,当我们还没有匹配项时会发生什么呢?如果我们在unchanged中传递success continuation,那么假设找到了匹配项,它最终将以0
作为参数调用。但是,这会丢失关于我们已经尝试调用它一次的信息。我们可以通过修改continuation来纠正这一点
case as of
...
(a:as') ...
| otherwise -> index x as' (fun idx -> success (idx + 1)) failure
考虑它的另一种方式是,我们在continuation中有collect“post”操作,因为最终计算结果将通过该代码
-- looking for the value 5, we begin by recursing
1 :
2 :
3 :
4 :
5 : _ -- match at index 0; push it through the continuation
0 -- lines from here down live in the continuation
+1
+1
+1
+1
如果我们以无点风格编写递归分支,这可能会更加清楚
| otherwise -> index x as' (success . (+1)) failure
这显示了我们如何修改continuation,为每个递归调用增加一个增量
index :: Eq a => a -> [a] -> (Int -> r) -> r -> r
index x as success failure
case as of
[] -> failure
(a:as') | x == a -> success 0
| otherwise -> index x as' (success . (+1)) failure
我宁愿使用类型签名index::eqa=>a->[a]->(Int->r)->r
。如果不指定继续的结果类型,那么在让类型引导您的同时,您就不会有犯错误的自由了。@kosmikus true,也许我假设Int
早到;)-您认为如果我重写它会更清晰吗?作为一行:索引x xs s f=foldr(\x'ri->如果x==x',那么s I其他r(成功)(常数f)xs 0
| otherwise -> index x as' (success . (+1)) failure
index :: Eq a => a -> [a] -> (Int -> r) -> r -> r
index x as success failure
case as of
[] -> failure
(a:as') | x == a -> success 0
| otherwise -> index x as' (success . (+1)) failure