派生推断类型的组合函数Haskell:具体(。)map uncurry

时间:2014-05-11 19:23:34

标签: haskell types ghci unification inferred-type

这里有很多线程来推导组合函数的推断类型,但我仍然相当困惑。我发现的帖子都没有给出关于如何统一类型的一般性解释。

我的一本考试指南中存在问题,而我无法搞清楚。

8)什么是(。)地图的推断类型uncurry :: ________

我能够在大多数时间推导推断类型,但我仍然有点困惑。例如,我知道要获得(。)map uncurry的答案,您需要首先派生地图类型uncurry。我能够做到这一点

鉴于以下类型

map  :: (a -> b) -> [a] -> [b]
uncurry :: (a -> b -> c) -> (a, b) -> c 

我将地图中的函数(a - > b)统一为uncurry so

a = a → b → c
b = (a, b) → c

然后答案是地图的另一半[a] - > [b]使用a和b的新值

map uncurry :: [ a -> b -> c ] -> [ (a, b) -> c]

然后你需要统一

(.)  :: (b -> c) -> (a -> b) -> a -> c
map uncurry :: [ a -> b -> c ] -> [ (a, b) -> c]

答案应该是

(.) map uncurry :: (a -> b1 -> b) -> [(a, b1)] -> [b]

但我不明白b1来自哪里或基本上是如何完成的。 我认为我真正需要的是对一般类型统一的解释。统一两种类型的方法是什么?如何知道两种类型不能统一。

如果有人能够逐步解释如何导出(。)地图不合理,我会非常感激。

2 个答案:

答案 0 :(得分:6)

确定导出类型签名的想法:

  1. 确保在开始之前就掌握了操作员和功能优先权 请注意,函数应用程序具有最高优先级并且与左侧相关联,因此:
  2. 第一个论点是最重要的一个 - 首先处理它,然后
  3. 在类型签名中,->与右侧相关联。
  4. 获得第一个参数的类型后,在所出现的任何地方替换该类型。
  5. 让我们通过你的榜样。

    <强>类型

    (.)  :: (b -> c) -> (a -> b) -> a -> c
    map  :: (a -> b) -> [a] -> [b]
    uncurry :: (a -> b -> c) -> (a, b) -> c 
    

    为每个函数指定不重叠的类型名称

    首先,这很令人困惑,因为有很多a s并且它们都不是同一个东西,所以我要用新字母重命名这些类型时间。

    (.)  :: (b -> c) -> (a -> b) -> a -> c
    map  :: (d -> e) -> [d] -> [e]
    uncurry :: (f -> g -> h) -> (f, g) -> h 
    

    对类型进行括号,与右侧相关联

    (.)  :: (b -> c) -> ((a -> b) -> a -> c)
    map  :: (d -> e) -> ([d] -> [e])
    uncurry :: (f -> (g -> h)) -> ((f, g) -> h)
    

    将第一个参数的类型与整个参数

    匹配

    现在让我们看一下(.) map uncurry这个词。正如您现在意识到的那样,将运算符.放在括号中会将其转换为遵循正常函数规则的函数,因此 (.) map uncurry表示((.) map) uncurry,首先要统一的类型来自(.)map

    现在(.)有第一个参数(b->c),因此(b->c)必须与map的类型统一:

    (.)  :: (   b     ->      c      )   -> ((a -> b) -> (a -> c))
    map  ::  (d -> e) -> ([d] -> [e])
    

    当我们将b ~ (d->e)c ~ ([d]->[e])替换为我们获得的(.)类型时:

    (.) :: ((d->e) -> ([d]->[e]))   ->  ((a -> (d->e)) -> (a -> ([d]->[e])))
    

    所以map成为第一个参数,因此当我们提供它时,它会从类型签名中消失,给出

    (.)           map               ::  ((a -> (d->e)) -> (a -> ([d]->[e])))
    

    检查解释器,如ghci或拥抱

    Hugs> :t (.) map
    (map .) :: (a -> b -> c) -> a -> [b] -> [c]
    

    是的 - 当我们添加由->为右关联的括号时,它们是相同的。

    转到下一个参数

    好的,现在我们有了

    (.) map :: ((a -> (d->e)) -> (a -> ([d]->[e])))
    uncurry :: (f -> (g -> h)) -> ((f, g) -> h)
    

    现在它非常诱人地匹配这两个第一个参数,因为它们看起来相同,但当然我们需要将(.) map第一个参数与整个类型的uncurry

    将第一个参数的类型与整个参数的类型匹配

    (.) map :: ((     a      -> (  d   -> e)) -> (a -> ([d]->[e])))
    uncurry ::   (f->(g->h)) -> ((f,g) -> h)
    

    提供a ~ (f->(g->h))d ~ (f,g)e ~ h

    (.) map :: (((f->(g->h)) -> ((f,g)-> h)) -> ((f->(g->h)) -> ([(f,g)]->[h])))
    

    并将其应用于uncurry给出了

    (.) map            uncurry               :: ((f->(g->h)) -> ([(f,g)]->[h])))
    

    与口译员核实

    Hugs> :t (.) map uncurry
    map . uncurry :: (a -> b -> c) -> [(a,b)] -> [c]
    

    太棒了 - 我们做到了!

    与经营者打交道

    如果我们采用示例length . map (.) $ repeat id ++ [] ++ [],我们将需要所有这些运算符的固定性,但我们可以先将函数应用程序括起来,因为它们优先:

    length . map (.) $ repeat id ++ [] ++ []
    length . (map (.)) $ (repeat id) ++ [] ++ []
    

    按优先顺序排列运算符

    使用解释器的:i命令查找运算符的固定性,并按顺序排列,最高位:

    infixr 9 .
    infixr 5 ++
    infixr 0 $
    

    最高优先级运算符.首先被括起来:

    (length . (map (.))) $ (repeat id) ++ [] ++ []
    

    然后是++,它与右边相关联:

    (length . (map (.))) $ ((repeat id) ++ ([] ++ []))
    

    并且$只使用了++,所以我并不打算将它包围起来。

    如果您愿意,可以将(++)等运算符转换为函数{{1}},但我完全不相信会对您有所帮助,所以我会留下它,只记得第一个参数在左边。

    它通常有助于从最嵌套的括号开始。

答案 1 :(得分:0)

(.) map uncurry相当于((.) map) uncurrymap功能未应用于uncurry,会传递给(.)。然后,您希望推断为函数的结果将uncurry作为参数接收。

至于b1的来源,不要忘记类型变量名称并不重要,只要您同样重命名给定类型变量的所有匹配项,就可以重命名它们。所以这个:

(.) map uncurry :: (a -> b1 -> b) -> [(a, b1)] -> [b]

相当于:

(.) map uncurry :: (apple -> pear -> plum) -> [(apple, pear)] -> [plum]