某人写下这样的foldlM定义需要什么知识或培训?[关闭]


9

最近,我试图在我的一些实际案例制作系统中使用Haskell。Haskell类型系统确实为我提供了很大的帮助。例如,当我意识到我需要某种类型的功能时

f :: (Foldable t, Monad m) => ( a-> b -> m b) -> b -> t a -> m b

实际上有类似的功能foldMfoldlMfoldrM

但是,真正令我震惊的是这些功能的定义,例如:

foldlM :: (Foldable t, Monad m) => (b -> a -> m b) -> b -> t a -> m b
foldlM f z0 xs = foldr f' return xs z0
  where f' x k z = f z x >>= k

因此函数f'必须为以下类型:

f' :: a -> b -> b

根据的要求foldr,则b必须是实物*-> m *,因此的整个定义是foldlM有意义的。

另一个示例包括liftA2和的定义<*>

(<*>) :: f (a -> b) -> f a -> f b
(<*>) = liftA2 id

liftA2 :: (a -> b -> c) -> f a -> f b -> f c
liftA2 f x = (<*>) (fmap f x)

在探究源代码之前,我尝试了一些自己的解决方案。但是差距是如此之大,以至于无论将来我要编写多少行代码,我都无法提出这种解决方案。

所以我的问题是,某人在如此高度抽象的水平上进行推理,需要哪种知识或特定的数学分支。

我知道类别理论可能会提供一些帮助,并且我一直在关注这个出色的演讲很长时间,并且仍在研究中。


13
Haskell是一种语言。它有很多单词,其中大多数单词可以多种不同方式使用。当您学习一种新语言时,多年来很多句子和习语都没有意义。但是,您使用它的次数越多,您就越会看到熟悉的模式,并且您曾经认为令人生畏和高级的事物自然而然地出现了。放松。
luqui

Answers:


3

我想,通常来说,逻辑等等。但是您也可以通过学习来学习它。:)随着时间的流逝,您会注意到一些模式,掌握一些技巧。

像这样,foldr还有一个额外的争论点。有人认为它可以折叠成函数,因此可以通过.id有时确实是<=<return)进行组合,

foldr g z xs  =  foldr ($) z . map g $ xs
              =  foldr (.) id (map g xs) z
         ~/=  foldr (<=<) return (map g xs) z
{-
  (\f -> f . f) :: (a -> a) -> (a -> a)

  (\f -> f <=< f) :: Monad m => (a -> m a) -> (a -> m a)
                            (still just a type, not a kind)
-}

有些人发现更容易用简单的语法术语来理解它,例如

foldr g z [a,b,c,...,n] s =
     g a (foldr g z [b,c,...,n]) s

因此,举例来说,当g在第二个参数中为non-strict 时s,即使我们在右侧折叠,也可以用作从左侧传递的状态。


1
非常感谢,我试图弄清楚这个定义是否唯一,并且没想到这里会使用Kleisli组合物。这个答案确实解决了我的疑问。
Theodora

别客气。:)
威尔·内斯

4

因此,了解它的最好方法就是这样做。下面有一个foldlMusing foldl代替的实现foldr。这是一个很好的练习,请尝试一下,然后再提出我建议的解决方案。该示例说明了我为实现此目标所做的所有推理,这可能与您的不同,并且可能有偏见,因为我已经知道使用函数累加器。

第1步:让我们试着写foldlM在条款foldl

-- this doesn't compile because f returning type is (m b) and not just (b) 
foldlM :: (Foldable t, Monad m) => (b -> a -> m b) -> b -> t a -> m b
foldlM f z0 xs = foldl f z0 xs 

-- So let substitute f by some undefined f'
foldlM :: (Foldable t, Monad m) => (b -> a -> m b) -> b -> t a -> m b
foldlM f z0 xs = foldl f' z0 xs
  where f' = undefined

-- cool, but f' should use f somehow in order to get the monadic behaviour
foldlM :: (Foldable t, Monad m) => (b -> a -> m b) -> b -> t a -> m b
foldlM f z0 xs = foldl f' z0 xs
  where f' b a = f somethingIDontkNow 

在这里,您意识到这f'是纯粹的,您需要提取结果以f进行类型匹配。唯一提取单调值的方法是使用>>=运算符,但是使用此类运算符后必须立即对其进行换行。

因此得出一个结论:每次结束时,我都想完全拆开此monad,请放弃。不是正确的方法

第2步:让我们尝试使用foldlMfoldl但首先使用[]可折叠,因为它很容易进行模式匹配(即,我们实际上不需要使用fold

-- This is not very hard. It is pretty standard recursion schema. :)
foldlM' :: (Monad m) => (b -> a -> m b) -> b -> [a] -> m b
foldlM' f z0 []     = return z0
foldlM' f z0 (x:xs) = f z0 x >>= \c -> foldlM' f c xs

好的,那很容易。让我们将定义与foldl列表的常规定义进行比较

foldlM' :: (Monad m) => (b -> a -> m b) -> b -> [a] -> m b
foldlM' f z0 []     = return z0
foldlM' f z0 (x:xs) = f z0 x >>= \c -> foldlM' f c xs

myfoldl :: (b -> a -> b) -> b -> [a] -> b
myfoldl f z0 []     = z0
myfoldl f z0 (x:xs) = foldl f (f z0 x) xs

凉!!他们几乎一样。琐碎的事情是完全相同的事情。递归的情况有些不同,您想编写更多类似的内容:foldlM' f (f z0 x) xs。但是is不像第1步那样进行编译,因此您可能会认为还可以,我不想应用f,只是持有这样的计算并用进行组合>>=。我想写些更有 foldlM' f (f z0 x >>=) xs意义的东西 ...

步骤3意识到要累积的是函数组成而不是结果。(在这里,我可能已经对它有所了解,因为您已经发布了它)。

foldlM :: (Foldable t, Monad m) => (b -> a -> m b) -> b -> t a -> m b
foldlM f z0 xs = foldl f' initFunc xs
  where initFunc = undefined :: b -> m b
        f'       = undefined :: (b -> m b) -> a -> (b -> m b) -- This type signature can be deduce because f' should be applied to initFunc and a's from t a. 

通过initFunc第二步(递归定义)的知识和使用我们的知识,我们可以推论得出initFunc = returnf'知道f'应该使用f和可以完成的定义>>=

foldlM :: (Foldable t, Monad m) => (b -> a -> m b) -> b -> t a -> m b
foldlM f z0 xs = foldl f' return xs z0
--                        ^^^^^^
--                        |- Initial value
  where f' b a = \bvalue -> b bvalue >>= \bresult -> f bresult a -- this is equivalent to (b >=> \result -> f result a) which captures the sequence behaviour of the implementation
--         ^      ^^^^^^                  ^^^^^^^
--         |      |                       |- This is the result of previous computation
--         |      |- f' should return a function b -> m b. Any time you have to return a function, start writing a lambda  
--         |- This b is the accumulated value and has type b -> m b
-- Following the types you can write this with enough practise

如您所见,做到这一点并不难。它需要实践,但是我不是专业的haskell开发人员,我自己可以做,这是一个实践问题


1
我真的看不出是什么让左折比右折更容易理解。正确的折叠更有可能对无限的结构产生有用的结果,并且对于典型的Monad实例更有效。
dfeuer

@dfeuer这样做的目的不是要显示一个简单的示例,而是要为OP提出合适的练习并公开解决方案的合理论据,试图证明并不需要成为超级高手Haskeller才能获得这样的解决方案。没有考虑效率问题
lsmor

3

您不需要任何数学方面的专门知识即可编写类似的函数foldM。我已经在生产环境中使用Haskell已有4年了,而且我也很难理解的定义foldM。但这主要是因为它写得不好。请,如果您无法理解一些晦涩的代码,请不要将其视为个人过失。这是更易读的foldlM

foldlM
    :: forall t m a b .
       (Foldable t, Monad m)
    => (b -> a -> m b)  -- ^ Monadic action
    -> b                -- ^ Starting accumulator
    -> t a              -- ^ List of values
    -> m b              -- ^ Computation result inside a monad
foldlM f z xs = (foldr step pure xs) z
  where
    step :: a -> (b -> m b) -> b -> m b
    step cur next acc = do
        result <- f acc cur
        next result

此功能仍然不是最简单的功能。主要是因为它foldr在中间累加器是函数的地方具有非典型用法。但是您会看到一些使这种定义更具可读性的可能:

  1. 关于函数参数的注释。
  2. 更好的参数名称(仍然简短且惯用,但至少更具可读性)。
  3. 内部函数的显式类型签名where(因此您知道参数的形状)。

看到这种功能之后,您现在可以执行方程式推理技术来逐步扩展定义并查看其工作原理。具备此类功能的能力会随经验而增加。我没有很强的数学家技能,并且此函数不是典型的Haskell函数。但是练习越多,它就会越好:)

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.