参考Haskell列表中的上一个术语

时间:2014-05-31 10:02:01

标签: haskell

我希望能在Haskell中做些什么,但如果可能的话,这一点并不明显。我是一个新手(例如,我还不了解monad),所以也许有一些先进的方法。

假设我已经将某个类型的函数f定义为自身。我希望能够定义一个概念“prev”,它代表列表中的前一个元素(给定类型的元素)。所以我想像[x,y,f prev]这样的意思是[x,y,f y]。我不介意定义是否涉及用自然数字压缩列表,只要我最后编写的内容可以隐藏构造方法。

这可能是不可能的一个原因是,如果有可能,那么一个人也应该能够定义“下一个”的概念,并且人们不希望能够写出[f next,g prev] 。如果不可能,还有下一个最好的选择吗?

2 个答案:

答案 0 :(得分:5)

我不认为有一种方法可以引入一个新的关键字prev来获得您描述的确切语法,至少缺少一些对这种情况来说显着过度的技术,比如隐式参数或模板Haskell。

但是,您可以使用名为tying the recursive knot的技术

来执行此操作
type PrevList a = [a -> a]

instantiatePrevList :: PrevList a -> [a]
instantiatePrevList prevList =
    let result =
           zipWith (\f prev -> f prev)
                   prevList
                   (error "Initial element has no previous element":result)
    in result

xs = instantiatePrevList [\_ -> 5, \_ -> 3, \prev -> prev + 1]

我们的想法是根据函数定义您的列表,这些列表总是传递上一个值 - 上面的PrevList类型。如果您不需要特定元素,则可以选择忽略它。

然后我们需要一个函数将它们放在一起,这就是instantiatePrevList所做的。

请注意,列表result是根据自身定义的,这就是"打结"进来 - 它依赖于Haskell的懒惰来工作。

使用懒惰的另一种方式是第一个元素,它没有先前的元素。只要您不尝试使用列表第一个元素中的上一个值,就不会遇到错误。

正如你猜测的那样,同样的技术也可以用来定义一个next - 只要你不写一些实际上依赖于它自己的东西,它仍然可以正常工作。终止方式。

编辑:

实际上,带有隐式参数的解决方案并不复杂,而且它确实可以更好地编写列表的语法:

{-# LANGUAGE ImplicitParams, ImpredicativeTypes, ScopedTypeVariables #-}

type PrevList a = [(?prev :: a) => a]

instantiatePrevList :: PrevList a -> [a]
instantiatePrevList prevList =
    let result =
           zipWith (\(elem :: ((?prev :: a) => a)) prev ->
                             let ?prev = prev in elem)
                   prevList
                   (error "Initial element has no previous element":result)
    in result

xs = instantiatePrevList [5, 3, ?prev + 1]

你必须对它们有点小心,如果你试图嵌套它们,你可能会得到令人困惑的结果 - 例如在另一个PrevList内加PrevList

答案 1 :(得分:2)

您可以定义自己的打结组合器以获得类似

的内容
example :: [Int]
example = tie [ const 1, succ . prev, succ . prev, (*2) . next, const 100, const 20 ]
-- yields     [       1,           2,           3,         200,       100,       20 ]

直观地说,const value表示value是列表的元素。代替, operation . prev表示将operation应用于上一个获取此元素 元件。表达式operation . next的工作方式类似。

此外,您可以使用uncurry e..g。

来引用上一个和下一个
example2 :: [Int]
example2 = tie [ const 100, uncurry (+), const 5 ]
-- yields      [       100,         105,       5 ]

可能的实施是

tie :: [ (a,a) -> a ] -> [a]
tie = go (error "no previous element")
  where go _  []     = []
        go pr (f:fs) = this : rest
            where rest = go this fs
                  this = f (pr, head rest)

prev :: (a,a) -> a
prev = fst

next :: (a,a) -> a
next = snd

Live demo