2011-09-20 69 views
5

我正在逐步切换到F#,用于我的许多家庭项目,但我对如何将完整应用程序,特别是跨领域问题联系在一起存在困惑。F#应用程序结构日志记录/存储库等

在C#中,如果我想记录东西,我会使用依赖注入来传递一个ILogger到每个类中,然后这可以很容易地从代码中调用。我可以在我的测试中验证给定特定情况下的日志写入,通过传递模拟并验证它。

public class MyClass 
{ 
    readonly ILogger _logger; 
    public MyClass(ILogger logger) 
    { 
     _logger = logger; 
    } 

    public int Divide(int x, int y) 
    { 
     if(y == 0) 
     { 
      _logger.Warn("y was 0"); 
      return 0; 
     } 
     return x/y; 
    } 
} 

在F#我使用的模块多很多,因此上述将成为

module Stuff 

let divde x y = 
    match y with 
    | 0 -> 0 
    | _ -> x/y 

现在,如果我有一个模块调用记录我可以打开并从那里使用日志功能在y为0的情况下,但我如何将这个注入单元测试?

我可以让每个函数都有一个日志函数(字符串 - >单元),然后使用部分应用程序将它们连接起来,但这似乎是一个非常多的工作,就像创建一个新的函数一样,记录呼叫。是否有一个特定的模式或一点F#,我错过了,可以做到这一点? (我已经看到了kprintf函数,但我仍然不知道如何为各种测试场景指定函数,同时使用完整应用的具体实现)

同样,如何将存储库获取的数据?你需要实例化一些类并设置CRUD函数,或者有没有办法注入你打开哪些模块(除#define之外)

+0

HTTP:/ /stackoverflow.com/questions/2003487/architectural-thinking-in-functional-languages http://stackoverflow.com/questions/192090/how-do-you-design-a-functional-program –

回答

2

这是一个基本的答案。首先,你似乎认为类和模块是可以互换的。类封装了数据,在这个意义上,它更类似于记录和DU。另一方面,模块封装了功能(它们被编译为静态类)。所以,我想你已经提到了你的选择:部分函数应用程序,将函数作为数据传递,或依赖注入。对于你的特殊情况,保留你所拥有的东西似乎是最简单的。

另一种方法是使用预处理器指令来包含不同的模块。

#if TESTING 
open LogA 
#else 
open LogB 
#endif 

DI不一定不合身的功能性语言。值得指出的是,F#使得定义和实现的接口比C#更容易。

+0

嗯,所以可想而知,F#没有类只是记录和DU,那么日志记录将不得不进入模块,因为您通常需要记录操作(至少在调试中)而不是结果的操作。这意味着DI不在窗口中,所以只剩下部分应用程序。你能推荐任何关于功能性架构的书籍吗?由于它的存在了这么久,我敢肯定,这些事情已经被解决,但所有的书我看过专注于“小”,而不是“大” – Dylan

+0

我想我做到了。我记得也很早就跟着这样挣扎过。我预计“大型”架构在F#中会有完全不同。一些功能语言允许模块参数化。不幸的是,F#没有。面向对象仍然是一个很好的方法。我知道这个问题已经在SO上提出了几次。快速搜索出现了:http://stackoverflow.com/questions/2003487/architectural-thinking-in-functional-languages。可能有其他人。 – Daniel

+0

感谢您的链接,我早些时候做了一次搜索,但找不到任何实际提供任何指导的内容。我觉得你说得对与部分应用程序,虽然,我想这只有以前反正依赖应该开始由托管的应用程序来连线了上去封装的一个水平。 – Dylan

3

如果您不需要在运行时更改记录器,那么使用编译器指令或#if在记录器的两个实现之间进行选择(如Daniel所建议的)可能是最好的也是最简单的方法。

从功能的角度来看,依赖注入意味着与通过日志记录功能对所有代码进行参数化相同的事情。坏事是你需要在任何地方传播这个函数(并且这会使代码有点混乱)。你也可以在模块中创建一个全局可变变量,并将其设置为一个接口的实例 - 我认为这实际上是F#的完全可以接受的解决方案,因为你只需要在几个地方改变这个变量。

另一种(更“纯”的)替代方法是定义一个工作流程(又名monad)进行日志记录。只有在F#中编写所有代码时,这才是一个不错的选择。这个例子在我的书的第12章讨论,它是available as a free sample。然后,你可以写这样的事情:

let write(s) = log { 
    do! logMessage("writing: " + s) 
    Console.Write(s) } 

let read() = log { 
    do! logMessage("reading") 
    return Console.ReadLine() } 

let testIt() = log { 
    do! logMessage("starting") 
    do! write("Enter name: ") 
    let! name = read() 
    return "Hello " + name + "!" } 

这种方法的好处是,它是一个很好的功能性技术。测试代码应该很容易,因为它返回Log<'TResult>功能essenitally给你一个价值以及它的副作用的记录,所以你可以比较的结果!但是,这可能是一种矫枉过正的行为,因为您必须将使用日志记录的每个计算都包装在log { .. }块中。

+0

谢谢托马斯, 我仍然在弯是可变性是邪恶的。在我的C#代码中,我总是使依赖只读。如果我在代码中创建一个可变记录器,那么我想没有简单的方法来防止意外重新分配。我想,正如我在另一条评论中提到的那样,尽管应用程序的高层(派生功能)不会访问低层模块。 – Dylan

+0

@Dylan - 是的,模块的声明是顺序敏感的,所以你的日志记录模块将不得不先走。一般来说,可变性是邪恶的(在F#中)。但是,如果您不需要重新分配记录器,那么它就是最简单的选择。 –

0

这里是在F#C#代码实现,类似于最初的C#

module stuff 

type MyClass(logger:#Ilogger) = 
    member x.Divide a b = 
     if b=0 then 
      logger.Warn("y was 0") 
      0 
     else 
      x/y 

这应该让你使用你知道同样的技术从C#为您记录

+0

谢谢,但我宁愿使用C#这种类型的方法。我真的没有讲清楚,但它是更适合你会怎么做只使用记录和函数定义这种方法不类 – Dylan

+0

A小调没有 - 你不需要在这种情况下,'#ILogger'类型,因为F#在调用方法或构造函数时自动插入演员表。在这里使用哈希类型似乎有点奇怪 - 我想它甚至使整个类通用(参数化日志类型),这不需要在这里。 –

+0

@Tomas - 多数民众赞成什么时,我不打扰使用编译器来检查我的代码 –