函数组合是否依赖于部分应用程序?
观察以下具有一些函数调用重复的函数:
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
我对功能构成与部分应用程序的关联是否准确?
答案 0 :(得分:11)
实际上,如果你采用表达式
let getCells = Map.toSeq >> Seq.map snd >> Seq.toList
并尝试将其编译为独立表达式,您将收到编译器错误:
错误FS0030:值限制。已推断值'getCells'具有泛型类型 val getCells :(地图&lt;'_ a,'_ b&gt; - &gt;'_b列表)当'_a:比较
时 要么将'getCells'的参数显式化,要么如果你不打算将它作为泛型,那么添加一个类型注释。
它适用于您的情况的原因是因为您使用 getCells
函数和grid
,这意味着编译器推断它具有约束类型。
为了使其保持通用,您可以使用显式参数对其进行重新编写:
let getCells xs = xs |> Map.toSeq |> Seq.map snd |> Seq.toList
此表达式是Map<'a,'b> -> 'b list when 'a : comparison
类型的有效独立表达式。
>>
函数组合运算符使用的样式称为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
请注意,代替传递给Seq.map
的部分应用函数,您可以传递lambda表达式。 getCells
函数仍然是由其他函数组成的函数,但它不再依赖snd
的部分应用。
因此,部分(双关语)回答你的问题:功能组合不必依赖于部分功能组合。
在F#中,默认情况下,函数 curried 。这意味着所有函数都采用一个参数,并返回一个值。有时(经常),返回值是另一个函数。
例如,考虑Seq.map
函数。如果使用一个参数调用它,则返回值为另一个函数:
Seq.map snd
此表达式的类型为seq<'a * 'b> -> seq<'b>
,因为Seq.map snd
的返回值是另一个函数。
这意味着您可以对上述lambda表达式fun x -> snd x
执行 Eta reduction ,因为x
出现在表达式的两侧。结果只是snd
,整个表达式变为
let getCells xs = xs |> Map.toSeq |> Seq.map snd |> Seq.toList
正如您所看到的,部分应用程序不是必要的用于功能组合,但使其更容易。
使用管道运算符(|>
)的上述组合仍然依赖于函数Map.toSeq
,Seq.map
等的部分应用。为了证明构图不依赖于部分应用,这里是一个“公正”(与部分?(双关语)相反)的替代方案:
let getCells xs =
xs
|> (fun xs' -> Map.toSeq xs')
|> (fun xs' -> Seq.map (fun x -> snd x) xs')
|> (fun xs' -> Seq.toList xs')
请注意,此版本广泛使用lambda表达式而不是部分应用程序。
我不会以这种方式撰写功能;我只包括这个替代方案来证明它可以完成。
答案 1 :(得分:8)
组成取决于一等函数,而不是部分应用程序。
实施组合需要的是:
部分应用程序为合成创建了更多机会,但原则上您可以在没有它的情况下轻松定义函数合成。
例如,C#没有部分应用*,但只要签名匹配,您仍然可以将两个函数组合在一起:
Func<a, c> Compose<a, b, c>(this Func<a, b> f,
Func<b, c> g)
{
return x => g(f(x));
}
只是>>
,语法更加清晰:f.Compose(g)
。
然而,构图和部分应用之间存在一个有趣的联系。 >>
运算符的定义是:
let (>>) f g x = g(f(x))
所以,当您编写foo >> bar
时,您确实部分应用 (>>)
函数,即省略x
参数以获取{{1}部分结果。
但是,正如我上面所说,这并非严格必要。上面的fun x = g(f(x))
函数相当于F#&#39; Compose
运算符,并不涉及任何部分应用程序; lambdas以稍微冗长的方式扮演同样的角色。
*除非你手动实现它,否则没人做。即而不是写作
>>
你必须写
string foo(int a, int b)
{
return (a + b).ToString();
}
然后你就可以像在F#中一样分别传递每个参数。
答案 2 :(得分:1)
这更多是评论而不是答案,因为评论很重要。
我从堆栈中提取参数的方法是使用fun
关键字。
// Standard function with arguments
let add1 x y = x + y
// Function defining no arguments.
// I think of the arguments x and y as being on the stack.
let add2 = (+)
// Function defining no arguments.
// I think of the arguments x and y as being on the stack
// and being given the names x and y.
let add3 = fun x y -> x + y
let result1 = add1 1 2
printfn "add1: %A" result1
let result2 = add2 1 2
printfn "add2: %A" result2
let result3 = add3 1 2
printfn "add3: %A" result3
发送到F#interactive产生
val add1 : x:int -> y:int -> int
val add2 : (int -> int -> int)
val add3 : x:int -> y:int -> int
val result1 : int = 3
add1: 3
val it : unit = ()
val result2 : int = 3
add2: 3
val it : unit = ()
val result3 : int = 3
add3: 3
val it : unit = ()
以及使用function
关键字
let charType =
function
| n when n >= '0' && n <= '9' -> "number"
| l when l >= 'a' && l <= 'z' -> "lower case"
| u when u >= 'A' && u <= 'Z' -> "upper case"
| _ -> "other"
let result4 = charType '0'
printfn "charType '0': %A" result4