【问题标题】:Does Function Composition rely on Partial Application?功能组合是否依赖部分应用程序?
【发布时间】:2016-04-05 12:24:34
【问题描述】:

函数组合是否依赖部分应用程序?

这是我的理解:

观察以下函数调用重复的函数:

let updateCells (grid:Map<(int * int), Cell>) =

    grid |> Map.toSeq
         |> Seq.map snd
         |> Seq.fold (fun grid c -> grid |> setReaction (c.X, c.Y)) grid

let mutable _cells = ObservableCollection<Cell>( grid |> Map.toSeq
                                                      |> Seq.map snd
                                                      |> Seq.toList )
let cycleHandler _ = 

    self.Cells <- ObservableCollection<Cell>( grid |> cycleThroughCells
                                                   |> Map.toSeq
                                                   |> Seq.map snd
                                                   |> Seq.toList )

如果您注意到,以下代码出现在所有三个函数中:

grid |> Map.toSeq
     |> Seq.map snd

功能组合

在函数式编程中,我们可以将函数融合在一起,使它们成为一个函数。

为此,让我们从重复的函数序列中创建一个新函数:

let getCells = Map.toSeq >> Seq.map snd >> Seq.toList

现在,如果您细心,您会注意到我们在使用函数组合时不使用任何参数。因此,不使用网格值。这背后的原因是因为部分应用。

部分申请

我仍在学习所有这些函数式编程技术。但是,我的理解是,部分应用程序是函数式编程中的一种技术,它推迟了接受给定函数的完整参数集的需要。换句话说,部分应用是延迟接受给定函数的完整参数集的行为,其中期望最终客户端稍后将提供其余参数。至少,这是我的理解。

我们现在可以采用如下函数:

let updateCells (grid:Map<(int * int), Cell>) =

    grid |> Map.toSeq
         |> Seq.map snd
         |> Seq.fold (fun grid c -> grid |> setReaction (c.X, c.Y)) grid

并将其重构为:

let updateCells (grid:Map<(int * int), Cell>) =

    grid |> getCells
         |> Seq.fold (fun grid c -> grid |> setReaction (c.X, c.Y)) grid

我对函数组合与部分应用相结合的想法是否准确?

【问题讨论】:

  • 你的想法是准确的。我不会说函数组合依赖于部分应用程序,但可以使用它。我倾向于将提供的参数视为在堆栈上,并且当函数需要它们时,如果它们未定义为参数,它会将它们从堆栈中取出。
  • 是的,但我尝试在组合定义中提供参数,但它无法编译。身份证...
  • 你究竟做了什么没有通过编译?

标签: f# functional-programming


【解决方案1】:

泛型

其实,如果你拿这个表达方式

let getCells = Map.toSeq >> Seq.map snd >> Seq.toList

并尝试将其编译为独立表达式,您将收到编译器错误:

错误 FS0030:值限制。值“getCells”已被推断为具有泛型类型 val getCells : (Map -> '_b 列表) 当 '_a : 比较
将“getCells”的参数设为显式,或者,如果您不希望它是通用的,请添加类型注释。

它在您的情况下起作用的原因是因为您使用getCells 函数与grid,这意味着编译器推断它具有受约束的类型。

为了保持通用性,您可以使用显式参数重新表述它:

let getCells xs = xs |> Map.toSeq |> Seq.map snd |> Seq.toList

此表达式是Map&lt;'a,'b&gt; -&gt; 'b list when 'a : comparison 类型的有效独立表达式。

无积分

&gt;&gt; 函数组合运算符一起使用的样式称为point-free。它适用于部分应用程序,但并不完全相同。

应用

不过,这个例子中有一个偏函数应用的例子:

let getCells xs = xs |> Map.toSeq |> Seq.map snd |> Seq.toList

函数snd有以下类型:

'a * 'b -> 'b

这是一个接受单个参数的函数。

你也可以编写上面的getCells函数没有部分应用snd函数:

let getCells xs = xs |> Map.toSeq |> Seq.map (fun x -> snd x) |> Seq.toList

请注意,您可以传递一个 lambda 表达式,而不是传递给 Seq.map 的部分应用函数。 getCells函数仍然是由其他函数组成的函数,但不再依赖于snd的部分应用。

因此,部分(双关语)回答您的问题:函数组合不必依赖部分函数组合。

柯里化

在 F# 中,函数默认为 curried。这意味着所有函数都采用 one 参数,并返回一个值。有时(通常),返回值是另一个函数。

Seq.map 函数为例。如果使用 one 参数调用它,则返回值为 another 函数:

Seq.map snd

这个表达式的类型是seq&lt;'a * 'b&gt; -&gt; seq&lt;'b&gt;,因为Seq.map snd的返回值是另一个函数。

Eta 减少

这意味着您可以对上述 lambda 表达式 fun x -&gt; snd x 执行 Eta 归约,因为 x 出现在表达式的两侧。结果就是snd,整个表达式变成了

let getCells xs = xs |> Map.toSeq |> Seq.map snd |> Seq.toList

如您所见,部分应用对于函数组合来说并不是必要的,但它确实使它更容易

公正

上述使用管道运算符 (|&gt;) 的组合仍然依赖于函数 Map.toSeqSeq.map 等的部分应用。为了证明组合不依赖于部分应用,这里有一个“不偏不倚”(partial 的反义词?(双关语))替代方案:

let getCells xs =
    xs
    |> (fun xs' -> Map.toSeq xs')
    |> (fun xs' -> Seq.map (fun x -> snd x) xs')
    |> (fun xs' -> Seq.toList xs')

请注意,此版本广泛使用 lambda 表达式而不是部分应用程序。

我不会以这种方式编写函数;我只包括这个替代方案来证明它是可以做到的。

【讨论】:

  • 你为什么不这样组合函数呢?会有用例吗?比如根据实时用户输入即时构建?
  • 我问是因为一篇文章,“Why Concatenative Programming Matters”,它指出“在连接语言中,组合是隐式的。然而,这并不意味着函数应用变得显式——它实际上变得不必要了。事实证明,这一特殊事实使这些语言的构建、使用和推理变得更加简单。”也许也更简单,因为 Application 使用变量、lambdas 和应用程序,但 Composition 只使用函数和组合。
  • @MicroservicesOnDDD 我不会使用这样的显式 lambda 表达式,因为它们不是必需的。
【解决方案2】:

组合依赖于一等函数,而不是真正依赖于部分应用程序。

实现组合需要的是:

  • 函数必须能够作为参数并作为返回值返回
  • 函数签名必须是有效类型(如果您希望组合是强类型)

部分应用程序为组合创造了更多机会,但原则上您可以轻松定义没有它的函数组合。

例如,C#没有部分应用*,但你仍然可以将两个函数组合在一起,只要签名匹配即可:

Func<a, c> Compose<a, b, c>(this Func<a, b> f, 
                            Func<b, c> g) 
{
    return x => g(f(x));
}

这只是&gt;&gt;,语法更丑:f.Compose(g)

但是,组合和部分应用之间有一个有趣的联系。 &gt;&gt; 运算符的定义是:

let (>>) f g x = g(f(x))

因此,当您编写foo &gt;&gt; bar 时,您确实部分应用(&gt;&gt;) 函数,即省略x 参数以获得fun x = g(f(x)) 部分结果。

但是,正如我上面所说,这不是绝对必要的。上面的Compose函数相当于F#的&gt;&gt;操作符,不涉及任何偏应用; lambda 以稍微冗长的方式执行相同的角色。


* 除非您手动实现它,否则没人会这样做。 IE。而不是写

string foo(int a, int b) 
{ 
    return (a + b).ToString(); 
}

你必须写

Func<int, string> foo(int a) 
{ 
    return b => (a + b).ToString(); 
}

然后您就可以像在 F# 中一样分别传递每个参数。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-03-30
    • 1970-01-01
    • 2010-12-18
    • 1970-01-01
    • 1970-01-01
    • 2020-08-02
    • 2011-09-23
    相关资源
    最近更新 更多