是否可以折叠< *>在哈斯克尔?

时间:2014-08-08 08:57:54

标签: haskell

我想要实现像

这样的东西
fun1 f a_ziplist

例如

getZipList $ (\x y z -> x*y+z) <$> ZipList [4,7] <*> ZipList [6,9] <*> ZipList [5,10]

f = (\x y z -> x*y+z) 
ziplist = [[4,7],[6,9],[5,10]]

为此,我想以递归方式应用&lt; *&gt;喜欢

foldx (h:w) = h <*> foldx w
foldx (w:[]) = w

但似乎无法制作&lt; *&gt;递归的。

5 个答案:

答案 0 :(得分:4)

让我们玩ghci中的类型,看看他们带我们的位置。

λ import Control.Applicative

(<*>)

的类型
λ :t (<*>)
(<*>) :: Applicative f => f (a -> b) -> f a -> f b

foldr的类型:

λ :t Prelude.foldr
Prelude.foldr :: (a -> b -> b) -> b -> [a] -> b

也许我们可以使用(<*>)作为foldr的第一个参数传递的函数。会是什么类型的?

λ :t Prelude.foldr (<*>)
Prelude.foldr (<*>) :: Applicative f => f a -> [f (a -> a)] -> f a

因此,似乎它在应用上下文中使用初始值,并在应用上下文中使用函数列表,并返回另一个应用程序。

例如,使用ZipList作为应用程序:

λ getZipList $ Prelude.foldr (<*>) (ZipList [2,3]) [ ZipList [succ,pred], ZipList [(*2)] ]

结果是:

[5]

我不确定这是否是问题所针对的,但fold使用(<*>)似乎是一种自然的方法。

答案 1 :(得分:4)

如果ziplist参数必须是普通列表,则看起来不可能。这是因为fold f [a1,...,an]必须为每个n输入良好的类型,因此f必须是一个函数类型,每个n至少需要n个参数,因此无限制许多。

但是,如果您使用GADT列表类型,其中值将其长度显示为类型级别的自然,您可以实现类似于您想要的类型。

{-# LANGUAGE DataKinds, KindSignatures, TypeFamilies, GADTs #-}

import Control.Applicative

-- | Type-level naturals
data Nat = Z | S Nat

-- | Type family for n-ary functions
type family   Fn (n :: Nat) a b
type instance Fn Z     a b = b
type instance Fn (S n) a b = a -> Fn n a b

-- | Lists exposing their length in their type
data List a (n :: Nat) where
  Nil :: List a Z
  Cons :: a -> List a n -> List a (S n)

-- | General <*> applied to a list of arguments of the right length
class Apply (n :: Nat) where
   foldF :: Applicative f => f (Fn n a b) -> List (f a) n -> f b

instance Apply Z where
   foldF f0 Nil = f0

instance Apply n => Apply (S n) where
   foldF fn (Cons x xs) = foldF (fn <*> x) xs

test :: [(Integer,Integer,Integer)]
test = foldF (pure (,,)) (Cons [10,11] (Cons [20,21] (Cons [30,31] Nil)))
-- Result: [(10,20,30),(10,20,31),(10,21,30),(10,21,31)
--         ,(11,20,30),(11,20,31),(11,21,30),(11,21,31)]

答案 2 :(得分:2)

一般来说,折叠(<*>)因其类型而变得棘手,正如其他人所提到的那样。但是对于您的ziplist元素都属于同一类型的特定示例,您可以使用不同的方法,并使您的计算只需对f进行少量更改,以使其采用list参数而不是单个元素:

import Data.Traversable
import Control.Applicative

f = (\[x,y,z] -> x*y+z) 
ziplist = [[4,7],[6,9],[5,10]]

fun1 f l = getZipList $ f <$> traverse ZipList l

只使用Data.ListPrelude函数即可实现此目的:

fun1 f = map f . transpose

答案 3 :(得分:0)

  

为此,我想递归地应用<*>之类的

foldx (h:w) = h <*> foldx w
foldx (w:[]) = w
     

但似乎无法制作&lt; *&gt;递归的。

我认为你对左右相关性感到困惑。 danidiaz用foldr (<*>)重新表述了这一点,这对于这种分析非常有用。文档提供了a useful definition of foldr in terms of expansion

foldr f z [x1, x2, ..., xn] == x1 `f` (x2 `f` ... (xn `f` z) ...)

将此应用于您的案例:

foldr (<*>) z [x1, x2, ..., xn] == x1 <*> (x2 <*> ... (xn <*> z) ...)

注意parens。 <*>是左关联的,因此foldr扩展不同于:

x1 <*> x2 <*> ... <*> xn <*> z == ((... (x1 <*> x2) <*> ...) <*> xn) <*> z

让我们更多地考虑foldr (<*>)的含义。另一种思考方式是稍微重写一下:

flip (foldr (<*>)) :: Applicative f :: [f (a -> a)] -> f a -> f a

(a -> a)形式的类型通常称为 endomorphisms ,它们形成一个monoid,其中作为操作,id作为标识。 newtype中有一个Data.Monoid封装器:

newtype Endo a = Endo { appEndo :: a -> a }

instance Monoid (Endo a) where
    mempty = id
    mappend = (.)

这为我们提供了另一种思考foldr (<*>)的方式,通过Endo来表达{/ 1}}:

toEndo :: Applicative f => f (a -> a) -> Endo (f a)
toEndo ff = Endo (ff <*>)

然后foldr (<*>)基本上做的是减少这个幺半群:

foldrStar :: Applicative f => [f (a -> a)] -> Endo (f a)
foldrStar fs = mconcat $ map toMonoid fs

答案 4 :(得分:0)

你拥有的东西相当于zipWith3 (\x y z -> x*y+z) [4,7] [6,9] [5,10]

foldl <*> foldl <*> foldl :: (a -> b -> a) -> a -> [b] -> a {em}> > aa -> b -> a中的相同>> let xs = map ZipList [[4,7],[6,9],[5,10]] >> getZipList $ pure (\x y z -> x*y+z) <*> (xs!!0) <*> (xs!!1) <*> (xs!!2) [29,73] >> foldl (<*>) (pure (\x y z -> x*y+z)) xs <interactive>:1:6: Occurs check: cannot construct the infinite type: b = a -> b Expected type: f (a -> b) Inferred type: f b In the first argument of `foldl', namely `(<*>)' In the expression: foldl (<*>) (pure (\ x y z -> x * y + z)) xs >> :t foldl foldl :: ( a -> b -> a ) -> a -> [b] -> a >> :t (<*>) (<*>) :: (Applicative f) => f (a -> b) -> f a -> f b -- f (a -> b) = f b ,但是当您在第一个数字列表中应用三元函数时,您会得到一个列表二进制函数,然后是下一步的一元函数,最后只有数字(所有不同的类型,然后):

zipWithN

chi的答案解决了这个问题,但 arity是固定的(针对特定代码)。实际上,答案真正做的是定义ZipList的限制版本(嗯,这里,当与N应用程序一起使用时 - 显然,它适用于任何一般的应用程序) {1}}(但仅适用于a -> a -> a -> ... -> a类型的函数),而例如

zipWith7 :: (a -> b -> c -> d -> e -> f -> g -> h) -> 
            [a] -> [b] -> [c] -> [d] -> [e] -> [f] -> [g] -> [h]

(换句话说,zipWith3 (,,) [10,11] ([20,21]::[Integer]) ([30,31]::[Int]) works)。