多个级别的函子

Functor over multiple levels

提问人:coderodde 提问时间:9/29/2023 更新时间:11/7/2023 访问量:129

问:

我有这个蹩脚的尝试:

fmap2 :: (Functor f, Functor g) => (a -> b) -> f (g a) -> f (g b)
fmap2 f f2 = (fmap2 f . fmap f2)

它应该像这样工作:

fmap2 negate [[1,2], [3]] -- Returns [[-1,-2],[-3]]
fmap2 head [Just "abc",Nothing,Just "def"] -- Returns [Just 'a',Nothing,Just 'e']

请注意,这是一个练习。(我试了 1,5 个小时,但没有找到任何解决方案。

问:我在那里做错了什么?

列表 函数 Haskell 函数式编程 函子

评论

0赞 chi 9/29/2023
记下手头变量的所有类型。如果您不将 IMO 作为输入并将其视为变成 .好吧,把 then 作为单一论点。是什么类型?如何从中获得通缉令?f2fmap2h :: a->b?? :: f (g a) -> f (g b)hfmap h??

答:

4赞 lsmor 9/29/2023 #1

当你陷入这样的事情时。我建议使用类型孔,因为它们会引导您朝着正确的方向前进

fmap2 :: (Functor f, Functor g) => (a -> b) -> f (g a) -> f (g b)
fmap2 f nested_functor = fmap _something nested_functor 
--      |                |    |- notice this underscore
--      |                |- clearly we need fo fmap something over the nested_functor but we don't know what yet
--      |- Use better names usually help. Haskellers tend to use ver short names, but that's not appropiate always

如果您尝试编译此代码,会注意到 并会为您建议一个类型ghc_something

Found hole: _something :: g a -> g b <-- This is interesting
      Where: ‘a’, ‘g’, ‘b’ ... (blah blah blah, not interesting)
      Or perhaps ‘_something’ is mis-spelled, or not in scope

我们知道需要有给定的类型。因此:_somethingg a -> g bFunctor g

fmap2 :: (Functor f, Functor g) => (a -> b) -> f (g a) -> f (g b)
fmap2 f nested_functor = fmap something nested_functor
  where 
    -- remember something :: Functor g => g a -> g b
    something = undefined -- left as an exercise

评论

0赞 coderodde 9/30/2023
我还是不明白。
3赞 amalloy 9/30/2023
@coderodde 这不是一个富有成效的后续行动。除非你更详细地了解你做什么和不理解什么,否则没有人可以澄清你不理解的部分。
0赞 lsmor 10/1/2023
@coderodde。在您的问题中,可以使用三个“对象”:和来自约束。因此,您可以尝试的事物组合非常有限。 或或或涉及所有三个的组合。在这些梳子中,只有一个是有意义的/编译fnested_functorfmapFunctor gsomethingf nested_functorf fmapnested_functor fmapnested_functor ffmap ffmap nested functor
1赞 willeM_ Van Onsem 9/29/2023 #2

您应该使用函子映射作为内部结构的函数,因此如下所示:fmap

fmap2 :: (Functor f, Functor g) => (a -> b) -> f (g a) -> f (g b)
fmap2 f = fmap (fmap …)

我把填写部分作为练习。

评论

0赞 coderodde 9/30/2023
我还是不明白。
0赞 Enlico 10/6/2023 #3

当用于此时

fmap2 head [Just "abc",Nothing,Just "def"] -- Returns [Just 'a',Nothing,Just 'e']
  • 什么是类型?它head[Char] -> Char¹;
  • 什么类型?它。[Just "abc",Nothing,Just "def"][Maybe [Char]]

所以你有一个函数,你想把它应用到函子里面的 s,而函子又是函子里面的。[Char] -> Char[Char]Maybe[]

一个允许您通过其中一层,因此您需要 2 个才能通过 2 个。看:fmap

           head  $       "abc"                     ==       'a'
      fmap head  $  Just "abc"                     ==  Just 'a'
      fmap head  $             Nothing             ==           Nothing
fmap (fmap head) $ [Just "abc",Nothing,Just "def"] == [Just 'a',Nothing,Just 'e']

是的,是多态的,但在上述情况下,它是在 上操作的。head[a] -> a[Char]

0赞 Geoffrey Warne 11/7/2023 #4
import Test.QuickCheck

fmap2 :: (Functor f, Functor g) => (a -> b) -> f (g a) -> f (g b)
fmap2 = fmap . fmap

prop_negate :: Property
prop_negate = property (fmap2 negate [[1,2],[3]] == [[-1,-2],[-3]])

prop_head :: Property
prop_head = property (fmap2 head [Just "abc", Nothing, Just "def"]
                              == [Just 'a', Nothing, Just 'd'])

因此,让我们并排看一下这两个版本:

fmap2 :: (Functor f, Functor g) => (a -> b) -> f (g a) -> f (g b)
fmap2 f f2 = (fmap2 f . fmap f2)

fmap2 :: (Functor f, Functor g) => (a -> b) -> f (g a) -> f (g b)
fmap2 = fmap . fmap
-- or
fmap2 :: (Functor f, Functor g) => (a -> b) -> f (g a) -> f (g b)
fmap2 fab fga = (fmap . fmap) fab fga

在第一个版本中,您可以在其中引用,使其递归。我不认为你是故意的。嵌套函子不是递归的情况,因为第二个测试用例清楚地表明了列表函子中的函子——绝对不是递归重复。fmap2fmap2Maybe

下一个问题是给定给 的两个参数。在类型签名中,两个参数被正确标识为 1) 函数和 2) 嵌套函子,即类型为“f”的函子内类型为“g”的函子。这里的两个参数非常不同 -- 一个应用于嵌套函子的函数和嵌套函子本身。在函数绑定 () 中,这两个参数似乎是对称的——与第一个参数的位置相同——但正如我们刚才在类型签名中看到的那样,它们是两个不同的东西:一个函数和一个函子,所以很明显它们不能对称地使用。要么将这两个不同的东西视为两个函数或两个函子。我怀疑您正在考虑两个函子(f 型和 g 型)。似乎您正在尝试在此处分别覆盖两个嵌套函子。正如你所看到的,这两个函子应该映射在一起。fmap2a -> bfmap2 f f2 = (fmap2 f . fmap2 f2)fmap

第二个版本以两种形式编写。在第一种形式中,“无点”的写作方式,被简单地描述为两次的组成。由于我们想将函数应用于两个嵌套函子中类型为“a”的值,因此两次的组合会跳过第一个函子(类型 f),然后跳过第二个函子(类型 g),以获得类型“a”的值并应用转换函数。fmap2fmapa -> bfmap

在第二个版本的第二种形式中,参数是显式给出的。(它们只是在无点版本中隐含的,这应该很容易看到。这应该有助于澄清第一个版本中的错误参数。转换函数和嵌套函子扮演不同的角色。double 向下挖掘两次以获得类型“a”的值并应用函数 ()。fabfgafmapfgafaba -> b

因此,保留了嵌套函子的结构,即 的“fg”部分,但类型“a”的值可以转换为类型“b”的值(在 的情况下不一定不同,但可能不同,就像在我们的示例中接受字符串并返回一个字符的情况一样)。这样,应用于嵌套函子的 type 函数将产生相同的嵌套函子,内部(可能)有不同的值,这是编译器对类型签名的期望。fganegateheada -> b(f (g a))(f (g b))