撰写功能签名

时间:2016-07-27 14:39:16

标签: javascript haskell functional-programming composition function-composition

我读过g :: A - >的组成。 B和f :: B - > C,发音(“f由g组成”),导致A - >的另一个函数(箭头)。 C.这可以更正式地表达为

  

f•g = f(g)=组成::(B - > C) - > (A - > B) - > (A - > C)

上述成分是否也可以定义如下?请澄清。 在这种情况下,compose函数采用相同的两个函数f和g,并从A - >返回一个新函数。下进行。

  

f•g = f(g)=组成::((B - > C),(A - > B)) - > (A - > C)

3 个答案:

答案 0 :(得分:8)

首先,我们需要做一些正确的事情:

  • f ○ g表示与f(g)完全不同的内容。

    • 前者是一个函数,给定参数x,首先将其提供给g然后将结果传递给f ,输出最终结果,即f(g(x))
    • OTOH,f(g)表示您立即将函数f应用于 g,而无需等待任何参数。 (g碰巧有一个函数类型,但在函数式语言中,函数可以像任何其他值/参数一样传递。)

    除非你正在处理一些非常古怪的多态函数,否则其中一个将是错误的类型。例如,类型很好的组合可能是

    sqrt ○ abs :: Double -> Double
    

    而良好类型的应用程序可能是(至少在Haskell中)

    map(sqrt) :: [Double] -> [Double]
    

    我将在下面假设您正在谈论f ○ g

  • 必须为一个函数本身提供类型签名,而不是一个应用于某些参数的函数。这是很多人完全错误的事情:在f(x)中,你有一个函数f和一个参数x。但是f(x) 不是一个函数,只是将函数应用于值的结果值!所以,你不应该写f ○ g :: ...之类的内容(除非你实际上只是讨论了结果的类型)。最好只写○ :: ...(或者,在Haskell中,(○) :: ...)。

  • 功能箭头不是关联的。大多数数学家可能甚至不知道X -> Y -> Z应该是什么意思。像Haskell这样的语言意味着什么可能实际上有点令人惊讶:

    X -> Y -> Z  ≡  X -> (Y -> Z)
    

即。这是一个函数的类型,它首先只接受X类型的参数。结果将再次成为一个函数,但只接受Y类型的参数。如果您愿意,此函数将具有已内置的X值(在所谓的closure中,除非编译器对此进行优化)。给它Y值也可以让函数真正完成它的工作并最终产生Z结果。

此时你已经得到了答案:确实签名X -> Y -> Z(X, Y) -> Z基本相同。重写此过程的过程称为currying

特别回答您的问题:大多数语言通常不做任何调整,因此签名((B -> C), (A -> B)) -> (A -> C)实际上更正确。它对应于您可以调用的函数

   compose(f,g)

OTOH,咖喱签名(B -> C) -> (A -> B) -> (A -> C)意味着您需要逐个提供参数:

   compose(f)(g)

只有像Haskell这样的语言才是标准风格,但你不需要那些parens:以下所有内容在Haskell中解析相同

   compose(f)(g)
   compose f g
   (compose) f g
   (.) f g
   f . g

其中.实际上是composition operator,您可以从文档中看到类型

(.) :: (b -> c) -> (a -> b) -> a -> c

答案 1 :(得分:4)

由于您使用 Javascript 标记了您的问题,因此从Javascript的角度来看是一个答案。

假设我正确理解您的签名,您需要按如下方式调整组合函数:(f, g) => x => f(g(x));。当然,这有效,但你失去了灵活性,并且没有任何收获。

原始咖喱功能以咖喱形式定义,这意味着它总是需要一个参数。如果整个代码中的每个函数只需要一个参数,那么就没有更多的arity(在大多数情况下)。它被抽象掉了。 Currying有助于函数组合,因为函数总是返回单个值。 Curried函数就像构建块。你几乎可以把它们放在一起:



    const comp = f => g => x => f(g(x)),
     comp2 = comp(comp)(comp),
     add = y => x => x + y,
     inc = x => x + 1,
     sqr = x => x * x;
    
    console.log(comp(sqr)(inc)(2)); // 9
    console.log(comp(add)(sqr)(2)(3)); // 7
    console.log(comp2(sqr)(add)(2)(3)); // 25




正如你只能在后一种情况下看到的那样,我们必须考虑这个问题。

如果对代码库的每个功能始终如一地应用Currying,那么它只能发挥其优势,因为它具有系统效应。

答案 2 :(得分:3)

首先,更常用的是圆圈:f ∘ g

其次,它更适合发音为“f g”。 (“f由g组成”听起来像f,由g组成,而不是由两者组成的新函数。)

最后,这两种类型基本相同,区别仅在于您希望如何将函数传递给compose函数。第一个定义完全curried函数的类型,这样compose将一个函数作为参数,并返回一个新函数,它将第二个函数作为参数并返回组合。这意味着使用f :: B -> Cg :: A -> B,您可以定义(使用Haskell语法)

compose :: (B -> C) -> (A -> B) -> (A -> C)
compose f g = \x -> f (g x)

或未经证实的版本

compose' :: ((B -> C), (A -> B)) -> (A -> C)
compose' (f, g) = \x -> f (g x)

无论哪种方式,返回值都是一样的;唯一的区别在于参数的传递方式。你可以写h = compose f g或者你可以写h = compose' (f, g)