Haskell Continuation传递列表中元素的样式索引

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

为了练习Haskell,我尝试了一系列的例子。我目前正在学习continuation passing,但对于如何实现像find index of element in list这样的函数,我有点困惑,它的工作原理如下:

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
是什么(作为一个类型和一个值)-但是请记住,您典型的开始(如下所示)将是进行第一个continuation
id
,因此类型只能是
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