在花了很多时间阅读和思考之后,我想我终于掌握了 monad 是什么、它们如何工作以及它们有什么用处。我的主要目标是弄清楚 monad 是否可以应用于我在 C# 中的日常工作。
当我开始学习 monad 时,我的印象是它们很神奇,它们以某种方式使 IO 和其他非纯函数变得纯。
我理解 monad 对于 .Net 中的 LINQ 之类的东西的重要性,并且 Maybe 对于处理不返回有效值的函数非常有用。我也很欣赏限制代码状态和隔离外部依赖的需要,我希望 monad 也能帮助解决这些问题。
但我最终得出结论,用于 IO 和处理状态的 monad 是 Haskell 的必需品,因为 Haskell 没有其他方法可以做到这一点(否则,你无法保证顺序,并且一些调用会被优化离开。)但对于更主流的语言,monad 不能很好地满足这些需求,因为大多数语言已经很容易处理和声明和 IO。
所以,我的问题是,可以说 IO monad 真的只在 Haskell 中有用吗?在 C# 中实现 IO monad 是否有充分的理由?
最佳答案
在工作中,我们在最重要的业务逻辑部分使用 monad 来控制 C# 代码中的 IO。两个例子是我们的财务代码和为我们的客户找到优化问题解决方案的代码。
在我们的财务代码中,我们使用 monad 来控制 IO 写入和读取数据库。它本质上由一小组操作和一个用于 monad 操作的抽象语法树组成。您可以想象它是这样的(不是实际代码):
interface IFinancialOperationVisitor<T, out R> : IMonadicActionVisitor<T, R> {
R GetTransactions(GetTransactions op);
R PostTransaction(PostTransaction op);
}
interface IFinancialOperation<T> {
R Accept<R>(IFinancialOperationVisitor<T, R> visitor);
}
class GetTransactions : IFinancialOperation<IError<IEnumerable<Transaction>>> {
Account Account {get; set;};
public R Accept<R>(IFinancialOperationVisitor<R> visitor) {
return visitor.Accept(this);
}
}
class PostTransaction : IFinancialOperation<IError<Unit>> {
Transaction Transaction {get; set;};
public R Accept<R>(IFinancialOperationVisitor<R> visitor) {
return visitor.Accept(this);
}
}
本质上是 Haskell 代码
data FinancialOperation a where
GetTransactions :: Account -> FinancialOperation (Either Error [Transaction])
PostTransaction :: Transaction -> FinancialOperation (Either Error Unit)
连同用于在 monad 中构建操作的抽象语法树,本质上是自由 monad:
interface IMonadicActionVisitor<in T, out R> {
R Return(T value);
R Bind<TIn>(IMonadicAction<TIn> input, Func<TIn, IMonadicAction<T>> projection);
R Fail(Errors errors);
}
// Objects to remember the arguments, and pass them to the visitor, just like above
/*
Hopefully I got the variance right on everything for doing this without higher order types,
which is how we used to do this. We now use higher order types in c#, more on that below.
Here, to avoid a higher-order type, the AST for monadic actions is included by inheritance
in
*/
在真实的代码中,有更多这样的代码所以我们可以记住一些东西是由 .Select() 构建的而不是 .SelectMany()为了效率。包括中间计算在内的财务操作仍然具有类型 IFinancialOperation<T> .操作的实际执行是由解释器完成的,解释器将所有数据库操作包装在一个事务中,并处理在任何组件不成功时如何回滚该事务。我们还使用解释器对代码进行单元测试。
在我们的优化代码中,我们使用 monad 来控制 IO 以获取外部数据以进行优化。这允许我们编写不知道计算是如何组成的代码,这让我们可以在多个设置中使用完全相同的业务代码:
由于代码需要传递使用哪个monad,我们需要一个monad的显式定义。这是一个。 IEncapsulated<TClass,T>本质上意味着 TClass<T> .这让 c# 编译器可以同时跟踪 monad 类型的所有三个部分,从而克服了在处理 monad 本身时进行强制转换的需要。
public interface IEncapsulated<TClass,out T>
{
TClass Class { get; }
}
public interface IFunctor<F> where F : IFunctor<F>
{
// Map
IEncapsulated<F, B> Select<A, B>(IEncapsulated<F, A> initial, Func<A, B> projection);
}
public interface IApplicativeFunctor<F> : IFunctor<F> where F : IApplicativeFunctor<F>
{
// Return / Pure
IEncapsulated<F, A> Return<A>(A value);
IEncapsulated<F, B> Apply<A, B>(IEncapsulated<F, Func<A, B>> projection, IEncapsulated<F, A> initial);
}
public interface IMonad<M> : IApplicativeFunctor<M> where M : IMonad<M>
{
// Bind
IEncapsulated<M, B> SelectMany<A, B>(IEncapsulated<M, A> initial, Func<A, IEncapsulated<M, B>> binding);
// Bind and project
IEncapsulated<M, C> SelectMany<A, B, C>(IEncapsulated<M, A> initial, Func<A, IEncapsulated<M, B>> binding, Func<A, B, C> projection);
}
public interface IMonadFail<M,TError> : IMonad<M> {
// Fail
IEncapsulated<M, A> Fail<A>(TError error);
}
现在我们可以想象为我们的计算需要能够看到的 IO 部分创建另一个 monad 类:
public interface IMonadGetSomething<M> : IMonadFail<Error> {
IEncapsulated<M, Something> GetSomething();
}
然后我们可以编写不知道计算是如何组合在一起的代码
public class Computations {
public IEncapsulated<M, IEnumerable<Something>> GetSomethings<M>(IMonadGetSomething<M> monad, int number) {
var result = monad.Return(Enumerable.Empty<Something>());
// Our developers might still like writing imperative code
for (int i = 0; i < number; i++) {
result = from existing in r1
from something in monad.GetSomething()
select r1.Concat(new []{something});
}
return result.Select(x => x.ToList());
}
}
这可以在 IMonadGetSomething<> 的同步和异步实现中重用.请注意,在此代码中,GetSomething()即使在异步设置中,s 也会一个接一个地发生,直到出现错误。 (不,这不是我们在现实生活中构建列表的方式)
关于c# - IO monad 在像 C# 这样的语言中有意义吗,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/21364837/
如何在ruby中调用C#dll? 最佳答案 我能想到几种可能性:为您的DLL编写(或找人编写)一个COM包装器,如果它还没有,则使用Ruby的WIN32OLE库来调用它;看看RubyCLR,其中一位作者是JohnLam,他继续在Microsoft从事IronRuby方面的工作。(估计不会再维护了,可能不支持.Net2.0以上的版本);正如其他地方已经提到的,看看使用IronRuby,如果这是您的技术选择。有一个主题是here.请注意,最后一篇文章实际上来自JohnLam(看起来像是2009年3月),他似乎很自在地断言RubyCL
我正在尝试在Ruby中复制Convert.ToBase64String()行为。这是我的C#代码:varsha1=newSHA1CryptoServiceProvider();varpasswordBytes=Encoding.UTF8.GetBytes("password");varpasswordHash=sha1.ComputeHash(passwordBytes);returnConvert.ToBase64String(passwordHash);//returns"W6ph5Mm5Pz8GgiULbPgzG37mj9g="当我在Ruby中尝试同样的事情时,我得到了相同sha
C#实现简易绘图工具一.引言实验目的:通过制作窗体应用程序(C#画图软件),熟悉基本的窗体设计过程以及控件设计,事件处理等,熟悉使用C#的winform窗体进行绘图的基本步骤,对于面向对象编程有更加深刻的体会.Tutorial任务设计一个具有基本功能的画图软件**·包括简单的新建文件,保存,重新绘图等功能**·实现一些基本图形的绘制,包括铅笔和基本形状等,学习橡皮工具的创建**·设计一个合理舒适的UI界面**注明:你可能需要先了解一些关于winform窗体应用程序绘图的基本知识,以及关于GDI+类和结构的知识二.实验环境Windows系统下的visualstudio2017C#窗体应用程序三.
在我让另一个人重做我的前端UI之前,我的Rails应用程序运行平稳。我已经尝试解决此错误3天了。这是错误:Nosuchfileordirectory-identifyExtractedsource(aroundline#59):575859606162@post=Post.find(params[:id])authorize@postif@post.update_attributes(post_params)flash[:notice]="Postwasupdated."redirect_to[@topic,@post]else{"utf8"=>"✓","_method"=>"patc
我是PHP开发人员,目前我正在学习Rails(3),当然还有Ruby。Idon'twanttobelieveinmagic因此,我尽可能多地了解Rails“背后”发生的事情。我发现有趣的是ActiveRecord模型中的方法调用,如has_one或belongs_to。我试图重现它,并提供了一个天真的例子:#has_one_test_1.rbmoduleFooclassBasedefself.has_oneputs'Willitwork?'endendendclassModel2如我所料,只要运行这个文件就会输出“Willitwork?”。在搜索Rails源代码时,我找到了负责的函数:
defreverse(ary)result=[]forresult[0,0]inaryendresultendassert_equal["baz","bar","foo"],reverse(["foo","bar","baz"])这行得通,我想了解原因。有什么解释吗? 最佳答案 如果我使用each而不是for/in重写它,它看起来像这样:defreverse(ary)result=[]#forresult[0,0]inaryary.eachdo|item|result[0,0]=itemendresultendforainb基本上就
我如何做Ruby方法"Flatten"RubyMethod在C#中。此方法将锯齿状数组展平为一维数组。例如:s=[1,2,3]#=>[1,2,3]t=[4,5,6,[7,8]]#=>[4,5,6,[7,8]]a=[s,t,9,10]#=>[[1,2,3],[4,5,6,[7,8]],9,10]a.flatten#=>[1,2,3,4,5,6,7,8,9,10 最佳答案 递归解决方案:IEnumerableFlatten(IEnumerablearray){foreach(variteminarray){if(itemisIEnume
我最近从C#转向了Ruby,我发现自己无法制作可折叠的标记代码区域。我只是想到做这种事情应该没问题:classExamplebegin#agroupofmethodsdefmethod1..enddefmethod2..endenddefmethod3..endend...但是这样做真的可以吗?method1和method2最终与method3是同一种东西吗?还是有一些我还没有见过的用于执行此操作的Ruby惯用语? 最佳答案 正如其他人所说,这不会改变方法定义。但是,如果要标记方法组,为什么不使用Ruby语义来标记它们呢?您可以使用
什么是Linq聚合方法的ruby等价物。它的工作原理是这样的varfactorial=new[]{1,2,3,4,5}.Aggregate((acc,i)=>acc*i);每次将数组序列中的值传递给lambda时,变量acc都会累积。 最佳答案 这在数学以及几乎所有编程语言中通常称为折叠。它是更普遍的变形概念的一个实例。Ruby从Smalltalk中继承了这个特性的名称,它被称为inject:into:(像aCollectioninject:aStartValueinto:aBlock一样使用。)所以,在Ruby中,它称为inj
我正在学习Rails,我注意到Rails不断地使用诸如link_to之类的辅助方法,而不是仅仅使用普通的html。现在我可以理解为什么他们会使用他们会使用一些辅助方法,但我不明白为什么他们更喜欢辅助方法而不是直接编码html。为什么Rails更喜欢辅助方法而不是您必须手动编写html?为什么Rails团队做出这样的设计选择? 最佳答案 在Rails应用程序中,通常使用URL方法和内容方法生成链接,例如这绝对比将它们放入中更易于管理手动标记。">(您正在使用路由器生成这些URL,对吗?如果您硬编码/users/1并决定稍后将其设为/u