是否有一种优雅的方式让函数返回相同类型的函数(在元组中)

时间:2013-02-21 23:09:42

标签: haskell recursive-datastructures

我正在使用haskell来实现一个涉及返回值的函数的模式,以及它们自身(或相同类型的函数)。现在我已经实现了这个:

newtype R a = R (a , a -> R a)

-- some toy functions to demonstrate    
alpha :: String -> R String
alpha str
    | str == reverse str = R (str , omega)
    | otherwise          = R (reverse str , alpha)

omega :: String -> R String
omega (s:t:r)
    | s == t    = R (s:t:r , alpha)
    | otherwise = R (s:s:t:r , omega)

这些类型函数的驱动力是一个名为cascade的函数:

cascade :: (a -> R a) -> [a] -> [a]
cascade _ [] = []
cascade f (l:ls) = el : cascade g ls where
    R (el , g) = f l

它接受种子函数和列表,并返回通过将种子函数应用于列表的第一个元素而创建的列表,将其返回的函数应用于列表的第二个元素,依此类推等等

这是有效的 - 然而,在使用它来获得稍微有用的东西的过程中,我注意到很多次我的基本单位是函数,它们很少返回除了它们之外的函数;并明确声明一个函数返回自己变得有点单调乏味。我宁愿能够使用像Monad的return函数这样的东西,但是,我不知道bind会对这些类型的函数做什么,特别是因为我从来没有打算将它们与任何东西联系起来除了他们首先返回的功能。

试图把这个变成莫纳德,开始让我担心我做的事情是否有用,所以,简而言之,我想知道的是:

  • 我在做什么坏事?如果没有,
  • 我在做什么之前/我在这里重新发明轮子了吗?如果没有,
  • 有没有一种优雅的方式来做到这一点,或者我已经达到了这个目标并因为想要某种return模拟而贪婪?

(顺便说一下,除了'返回自己的功能'或'递归数据结构(功能)'之外,我不太清楚这种模式被称为什么,并且已经尝试在其中进行有效的研究 - 如果有人能给我一个这个模式的名称(如果它确实有一个),那将是非常有帮助的)

3 个答案:

答案 0 :(得分:7)

作为一个高级别的考虑,我会说你的类型代表一个有状态的流变换器。这里有点令人困惑的是你的类型被定义为

newtype R a = R (a , a -> R a)

而不是

newtype R a = R (a -> (R a, a))

这在流式上下文中会更自然,因为如果您还没有收到任何内容,通常不会“生成”某些东西。那么你的函数也会有更简单的类型:

alpha, omage :: R String
cascade :: R a -> [a] -> [a]

如果我们试图概括一下流变换器的概念,我们很快就会意识到将a列表转换为a列表的情况只是一个特例。有了适当的基础设施,我们也可以生成b的列表。因此,我们尝试推广类型R

newtype R a b = R (a -> (R a b, b))

我已经看到这种结构被称为Circuit,恰好是一个成熟的arrow。箭头是函数概念的概括,是比monad更强大的构造。我不能假装理解类别 - 理论背景,但与它们一起玩它绝对有趣。例如,简单的转换只是Cat.id

import Control.Category
import Control.Arrow
import Prelude hiding ((.), id)
import qualified Data.List as L

-- ... Definition of Circuit and instances

cascade :: Circuit a b -> [a] -> [b]
cascade cir = snd . L.mapAccumL unCircuit cir

--
ghci> cascade (Cat.id) [1,2,3,4] 
[1,2,3,4]

我们还可以通过参数化我们返回的电路来模拟状态:

countingCircuit :: (a -> b) -> Circuit a (Int, b)
countingCircuit f = cir 0
    where cir i = Circuit $ \x -> (cir (i+1), (i, f x))

--
ghci> cascade (countingCircuit (+5)) [10,3,2,11]
[(0,15),(1,8),(2,7),(3,16)]

我们的电路类型是一个类别的事实为我们提供了一个很好的组合电路的方法:

ghci> cascade (countingCircuit (+5) . arr (*2)) [10,3,2,11]
[(0,25),(1,11),(2,9),(3,27)]

答案 1 :(得分:1)

看起来你拥有的是流的简化版本。那就是 比如,表达无限的价值流。我认为你不能 很容易将其定义为monad,因为你为种子使用相同的类型 对于你的元素,这使得定义fmap变得困难(似乎你 需要将提供给fmap的函数反转,以便能够 恢复种子)。您可以通过制作种子类型使其成为monad 独立于元素类型,如此

{-# LANGUAGE ExistentialQuantification #-}
data Stream a = forall s. Stream a s (s -> Stream a)

这将允许您定义FunctorMonad实例,如下所示

unfold :: (b -> (a, b)) -> b -> Stream a
unfold f b = Stream a b' (unfold f)
    where (a, b') = f b

shead :: Stream a -> a
shead (Stream a _ _) = a

stail :: Stream a -> Stream a
stail (Stream _ b f) = f b

diag :: Stream (Stream a) -> Stream a
diag = unfold f
    where f str = (shead $ shead str, stail $ fmap stail str)

sjoin :: Stream (Stream a) -> Stream a
sjoin = diag

instance Functor Stream where
    fmap f (Stream a b g) = Stream (f a) b (fmap f . g)

instance Monad Stream where
    return   = unfold (\x -> (x, x))
    xs >>= f = diag $ fmap f xs

请注意,这只是在作​​为一组观看时遵守Monad法则,因为它没有 保留元素排序。

This解释 流monad使用无限列表,这在Haskell中也可以正常工作 因为它们可以以懒惰的方式生成。如果你看看 您将vector库中Stream类型的文档 找到一个更复杂的版本,以便它可以用于高效的流融合。

答案 2 :(得分:0)

我没有太多要补充的内容,除非要注意你的cascade函数可以写成左折(因此也可以作为右折,但我还没有完成转换。)< / p>

cascade f = reverse . fst . foldl func ([], f)
  where
    func (rs,g) s = let R (r,h) = g s in (r:rs,h)