实现`(->)((->)a b)`为应用函子的最佳方法是什么?

时间:2019-08-20 17:32:40

标签: haskell applicative

我正在研究Ninety-Nine Haskell Problems中的问题19,遇到了以下困难。问题要求“将列表向左旋转N个位置”。这很容易以明确的方式实现,例如

rotate :: [a] -> Int -> [a]
rotate xs n = drop n xs ++ take n xs

但是,对于我自己的教育和挑战,我想使用应用函子以一种无意义的方式实现这一点。例如,可以通过使用(->) [a]Applicative函数的事实消除参数的 one ,并实现rotate,如下所示:

rotate :: Int -> [a] -> [a]
rotate n = (++) <$> drop n <*> take n

理想情况下,人们应该能够消除两个 参数,并将其写为

rotate :: [a] -> Int -> [a]
rotate :: (++) <$> drop <*> take

但这会导致类型错误。 (我不确定确切如何推断类型,但问题似乎出在这样一个事实:所推断的Applicative函子是(->) Int而不是(->) ((->) Int [a])。)

解决此问题的一种方法是将(->) ((->) a b)手动实现为Applicative的实例,尤其是设置

<*> f g x y = f x y (g x y)

,但是似乎应该有一种更简洁的方法来内联。解决此问题的“正确”方法是什么?

2 个答案:

答案 0 :(得分:13)

有一种不使用Applicative实例的“最佳”方法。

import Data.Semigroup
rotate = drop <> take

我们可以明确地知道实例化(<>)的类型

{-# Language ScopedTypeVariables #-}
{-# Language TypeApplications    #-}

rotate :: forall a. Int -> [a] -> [a]
rotate = (<>) @(Int -> [a] -> [a]) drop take

使用以下实例解决:

instance Semigroup b => Semigroup (a -> b)
instance                Semigroup [a]

答案 1 :(得分:8)

两个选择:

rotate = liftA2 (liftA2 (++)) drop take
rotate = getCompose (liftA2 (++) (Compose drop) (Compose take))

在内联Compose的{​​{1}}实例的实例方法定义之后,后者变为前者。

当然,如果愿意,您可以恢复用ApplicativeliftA2来拼写(<$>)