Warning: file_get_contents(/data/phpspider/zhask/data//catemap/4/fsharp/3.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
惯用的实现方法";服务“;在F#_F# - Fatal编程技术网

惯用的实现方法";服务“;在F#

惯用的实现方法";服务“;在F#,f#,F#,在F#中工作并实现“服务”模式时,例如围绕web API或数据库或USB小工具的包装器,惯用的方法是什么?我倾向于像C#一样使用例如IDatabase和数据库,ITwitter和Twitter,ICamera和Camera接口和类,以便进行简单的模拟测试。但如果这不是标准的方法,我不想把“C”写成F 我考虑过使用DU来表示,例如,Camera | TestCamera,但这意味着将所有模拟代码放入生产代码库,这听起来很可怕。但也许这只是我的OO背景 我还考虑将IDatabase作为函数的记录。我

在F#中工作并实现“服务”模式时,例如围绕web API或数据库或USB小工具的包装器,惯用的方法是什么?我倾向于像C#一样使用例如
IDatabase
数据库
ITwitter
Twitter
ICamera
Camera
接口和类,以便进行简单的模拟测试。但如果这不是标准的方法,我不想把“C”写成F

我考虑过使用DU来表示,例如,
Camera | TestCamera
,但这意味着将所有模拟代码放入生产代码库,这听起来很可怕。但也许这只是我的OO背景

我还考虑将
IDatabase
作为函数的记录。我仍然对这个想法持开放态度,但它似乎有点做作。此外,它还排除了使用IoC控制器或任何“类似MEF”的插件功能的想法(至少我知道)


“惯用的F#”方法是什么?只需遵循C#服务模式?

在F#中使用接口没有什么错

教科书式的FP方法是让客户机将实现组件逻辑的函数作为参数,但这种方法不能随着这些函数数量的增加而很好地扩展。将它们组合在一起是一个好主意,因为这样可以提高可读性,在.NET中使用接口是一种惯用的方法。特别是在定义良好的组件的边界上,它是有意义的,我认为您引用的三个接口非常适合这种描述

我已经看到DU的使用方式与您描述的大致相同(提供了生产和假实现),但它也不适合我


如果有什么区别的话,函数的记录不是惯用的。在FP语言中,它们是一种穷人将行为捆绑在一起的方式,但如果有一件事面向对象语言做得对的话,那就是将行为捆绑在一起。接口只是一个更好的工具,尤其是因为F#允许您创建与对象表达式语法内联的实现。

正如另一个答案中提到的,在F#中使用接口是很好的,这可能是解决问题的一个好方法。然而,如果您使用更面向函数转换的编程风格,您可能不需要它们

理想情况下,大多数实际感兴趣的代码应该是不执行任何效果的转换,因此它们不会调用任何服务。相反,他们只接受输入并产生输出。让我演示如何使用数据库

使用
IDatabase
服务,您可以编写如下内容:

let readAveragePrice (database:IDatabase) = 
  [ for p in database.GetProducts() do
      if not p.IsDiscontinued then
        yield p.Price ]
  |> Seq.average
let calculateAveragePrice (products:seq<Product>) = 
  [ for p in products do
      if not p.IsDiscontinued then
        yield p.Price ]
  |> Seq.average
这样编写时,可以提供
IDatabase
的模拟实现,以测试
readAveragePrice
中的平均逻辑是否正确。但是,您也可以这样编写代码:

let readAveragePrice (database:IDatabase) = 
  [ for p in database.GetProducts() do
      if not p.IsDiscontinued then
        yield p.Price ]
  |> Seq.average
let calculateAveragePrice (products:seq<Product>) = 
  [ for p in products do
      if not p.IsDiscontinued then
        yield p.Price ]
  |> Seq.average
当然,这是一个过于简单的例子,但它表明了以下观点:

将数据访问代码推到核心逻辑之外,并将核心逻辑保持为实现转换的纯函数。然后您的核心逻辑将很容易测试-给定样本输入,它们将返回正确的结果


这里的其他人写道,在F.*中使用接口没有什么错,我认为接口只不过是一种互操作机制,它可以使F.*与其他.NET语言编写的代码一起工作。

当我用C#编写代码时,我遵循以下步骤。当一起使用时,和最终应为

因此,即使在C#中,正确设计的接口也应该只有一种方法:

public interface IProductQuery
{
    IEnumerable<Product> GetProducts(int categoryId);
}
在本例中,您会注意到,
getProducts
作为参数传递给
calculatearageprice
函数,其类型甚至没有声明。这非常符合让客户机定义接口的原则:参数的类型是从客户机的用法推断出来的。它的类型为
'a->#seq
(因为
categoryId
可以是任何泛型类型
'a


calculatearageprice
这样的函数是一个函数,这是一个功能性的事情。

几乎是重复的:我看到了推理和逻辑,但对结论有困难。给定的记录是一堆
get\uu
方法,这是否意味着它们的设计也很糟糕?在哪里/为什么画这条线?不过,这可能是一个较长的对话。具体来说,以
ICamera
为例,当前
getNewPhoto-camera-config
必须调用几个
camera.setwhater-cfg.whater
camera.snap()
camera.waitfile
,以及
camera.download
。将这些方法作为N个单独的参数传递给
getNewPhoto
,似乎很麻烦。您建议如何处理这样的事情?(注意,
getNewPhoto
函数在许多不同的地方使用,因此我不认为将其分解为更少的参数块并将其到处内联是明智的。)正如我在文章中所写的,如果您需要将所有这些函数作为参数传递给同一个函数,这可能是该函数做得太多的一个迹象。当你把SRP和ISP结合起来时,这也就有了一个实值。“记录是GETZ方法的捆绑包”,只有在考虑IL级实现时才是真的。记录是一组值,而不是方法。那些
get
方法本来可以作为字段来实现,这不会有什么区别;其他答案是基于这一点的细节。