我正在努力深入了解Monads。因此,我开始挖掘一下Maybe Monad。
有一件事我似乎没有做对。阅读本文:
“因此,Maybe Bind会发生短路。在任何操作链中,如果其中任何一个返回Nothing,评估将停止,并且将从整个链中返回任何内容。” < / p>
来自:http://mikehadlow.blogspot.com/2011/01/monads-in-c-5-maybe.html
而且:
“对于Maybe<T>
类型,绑定是根据一个简单的规则实现的:如果chain在某个时刻返回一个空值,则链中的其他步骤将被忽略,而空值则是returend”
来自:“C#中的函数式编程”http://www.amazon.com/Functional-Programming-Techniques-Projects-Programmer/dp/0470744588/
好的,我们来看看代码。这是我的可能Monad:
public class Maybe<T>
{
public static readonly Maybe<T> Empty = new Maybe<T>();
public Maybe(T value)
{
Value = value;
}
private Maybe()
{
}
public bool HasValue()
{
return !EqualityComparer<T>.Default.Equals(Value, default(T));
}
public T Value { get; private set; }
public Maybe<R> Bind<R>(Func<T, Maybe<R>> apply)
{
return HasValue() ? apply(Value) : Maybe<R>.Empty;
}
}
public static class MaybeExtensions
{
public static Maybe<T> ToMaybe<T>(this T value)
{
return new Maybe<T>(value);
}
}
这是我使用monad的示例代码:
class Program
{
static void Main(string[] args)
{
var node = new Node("1", new Node("2", new Node("3", new Node("4", null))));
var childNode = node.ChildNode
.ToMaybe()
.Bind(x => x.ChildNode.ToMaybe())
.Bind(x => x.ChildNode.ToMaybe())
.Bind(x => x.ChildNode.ToMaybe())
.Bind(x => x.ChildNode.ToMaybe())
.Bind(x => x.ChildNode.ToMaybe());
Console.WriteLine(childNode.HasValue() ? childNode.Value.Value : "");
Console.ReadLine();
}
}
public class Node
{
public Node(string value, Node childNode)
{
Value = value;
ChildNode = childNode;
}
public string Value { get; set; }
public Node ChildNode { get; private set; }
}
很明显,我们正在尝试深入挖掘节点树,而不是可能。但是,根据我提到的报价,我没有看到它是如何运作的。我的意思是,当然我已经考虑了空检查并且示例有效。但是,它并没有提前打破链条。如果设置断点,您将看到将使用每个Bind()
操作,因此没有最后一个操作的值。但这意味着,如果我挖20级深度并且实际上只下降3级,我仍然会检查20级或我错了吗?
将此与非monad方法进行比较:
if (node.ChildNode != null
&& node.ChildNode.ChildNode != null
&& node.ChildNode.ChildNode.ChildNode != null)
{
Console.WriteLine(node.ChildNode.ChildNode.ChildNode.Value);
}
这实际上不应该被称为短路吗?因为在这种情况下,if确实在第一个值为null的级别上中断。
有人能帮助我清楚说明吗?
更新
正如帕特里克所指出的那样,即使我们只有3个级别并且尝试深入20个级别,也会调用每个绑定。但是,不会评估提供给Bind()调用的实际表达式。我们可以编辑示例以使效果清晰:
var childNode = node.ChildNode
.ToMaybe()
.Bind(x =>
{
Console.WriteLine("We will see this");
return x.ChildNode.ToMaybe();
})
.Bind(x => x.ChildNode.ToMaybe())
.Bind(x => x.ChildNode.ToMaybe())
.Bind(x => x.ChildNode.ToMaybe())
.Bind(x =>
{
Console.WriteLine("We won't see this");
return x.ChildNode.ToMaybe();
});
答案 0 :(得分:11)
我在c#中实现了monad可能与你的有点不同,首先它与null检查无关,我相信我的实现更接近于在例如Haskel的标准实现中所发生的事情。 / p>
我的实施:
public abstract class Maybe<T>
{
public static readonly Maybe<T> Nothing = new NothingMaybe();
public static Maybe<T> Just(T value)
{
return new JustMaybe(value);
}
public abstract Maybe<T2> Bind<T2>(Func<T, Maybe<T2>> binder);
private class JustMaybe
: Maybe<T>
{
readonly T value;
public JustMaybe(T value)
{
this.value = value;
}
public override Maybe<T2> Bind<T2>(Func<T, Maybe<T2>> binder)
{
return binder(this.value);
}
}
private class NothingMaybe
: Maybe<T>
{
public override Maybe<T2> Bind<T2>(Func<T, Maybe<T2>> binder)
{
return Maybe<T2>.Nothing;
}
}
}
正如你在这里看到的那样,NothingMaybe的bind函数只返回一个新的东西,因此从不评估在binder表达式中传递的内容。它是短路的,因为一旦你进入“无状态”就不会再评估绑定表达式,但是绑定函数本身将被链中的每个monad调用。
这个实现可以用于任何类型的“不确定操作”,例如空检查或检查空字符串,这样所有这些不同类型的操作可以链接在一起:
public static class Maybe
{
public static Maybe<T> NotNull<T>(T value) where T : class
{
return value != null ? Maybe<T>.Just(value) : Maybe<T>.Nothing;
}
public static Maybe<string> NotEmpty(string value)
{
return value.Length != 0 ? Maybe<string>.Just(value) : Maybe<string>.Nothing;
}
}
string foo = "whatever";
Maybe.NotNull(foo).Bind(x => Maybe.NotEmpty(x)).Bind(x => { Console.WriteLine(x); return Maybe<string>.Just(x); });
这会向控制台打印“what”,但是如果值为null或为空,则无效。
答案 1 :(得分:4)
据我了解,将调用所有Bind
方法,但仅当前一个表达式返回值时才会计算所提供的表达式。这意味着在返回Bind
(或更正确地:null
)之后调用的default(T)
方法将非常便宜。
答案 2 :(得分:0)
我们可以更狡猾地做到这一点。
编写从IEnumerable派生的接口
public interface IOptional<T>: IEnumerable<T> {}
这将节省与LINQ方法的兼容性
public class Maybe<T>: IOptional<T>
{
private readonly IEnumerable<T> _element;
public Maybe(T element)
: this(new T[1] { element })
{}
public Maybe()
: this(new T[0])
{}
private Maybe(T[] element)
{
_element = element;
}
public IEnumerator<T> GetEnumerator()
{
return _element.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
在此之后我们可以使用LINQ的全部功能并且这样做
var node = new Node("1", new Node("2", new Node("3", new Node("4", null))));
var childNode =
new Some<Node>(node.ChildNode)
.SelectMany(n => new Maybe<Node>(n.ChildNode))
.SelectMany(n => new Maybe<Node>(n.ChildNode))
.SelectMany(n => new Maybe<Node>(n.ChildNode))
.SelectMany(n => new Maybe<Node>(n.ChildNode))
.SelectMany(n => new Maybe<Node>(n.ChildNode));
Console.WriteLine(childNode.Any() ? childNode.First().Value : "");