【问题标题】:What causes error "The type of the expression in the let clause is incorrect."什么导致错误“let 子句中的表达式类型不正确。”
【发布时间】:2016-02-19 22:00:22
【问题描述】:

我已经实现了一个 State monad,并且正在努力使用扩展方法启用 LINQ“理解语法”。

谁能提供有关标题错误原因的见解?可能缺少扩展方法,或者签名错误。令人费解的是,LINQ 更详细的方面可以工作,而像 let 子句这样看似微不足道的事情却没有。

请注意:其他地方有很多关于“select”或“join”子句的类似错误的帖子;我无法辨别出与下面的案例有任何相似之处。

这个例子可以正确编译和运行:

namespace StateMonad {
  using StateUnit = State<Unit,GCDState>;
  using TupleUnit = State<Unit,GCDState>.StateTuple;

  private static class TestClass  {

    // This usage compiles and runs fine.
    private static readonly StateUnit GcdInner_Good =
      ( from s in State<Unit,GCDState>.Get
        select new TupleUnit(Unit.unit, 
              s.A > s.B ? new GCDState(s.B, s.A-s.B)
            : s.A < s.B ? new GCDState(s.B,   s.A  )
                        : s)
      );
// continued below

但是这个没有,在第一个let上产生了感兴趣的错误:

// continued from above  

    // This usage fails to compile, with error as shown
    private static readonly StateUnit GcdInner_Bad =
      ( from s in State<Unit,GCDState>.Get
        let A = s.A       // Generates error on "let":
        let B = s.B       //   "The type of the expression in the let clause is incorrect.
                          //    Type inference failed in the call to 'Select'."
        select new TupleUnit(Unit.unit, 
              A > B ? new GCDState(B, A - B)
            : A < B ? new GCDState(B,   A  )
                        : s)
      );
  }
}

这里是到目前为止声明的扩展方法:

  public static class StateExtensions {
    [Pure]public static State<TResult,TState>    Select<TValue,TState,TResult>( this
      State<TValue,TState> @this,
      Func<TValue,State<TResult,TState>.StateTuple> projector
    ) where TResult:struct where TState:struct where TValue:struct {
      projector.ContractedNotNull("projector");

      return new State<TResult,TState>(s => projector(@this.EvalState(s)));
    }

    [Pure]public static State<TResult,TState>    SelectMany<TValue,TState,TResult>( this
        State<TValue,TState> @this,
        Func<TValue,State<TResult,TState>> selector
    ) where TResult:struct where TState:struct where TValue:struct {
        selector.ContractedNotNull("selector");

        return @this.Bind(selector);
    }

    [Pure]public static State<TResult,TState>   SelectMany<TValue,TState,T,TResult>( this
        State<TValue,TState> @this,
        Func<TValue, State<T,TState>> selector,
        Func<TValue, T, TResult> projector
    ) where TResult:struct where TState:struct where TValue:struct where T:struct {
        selector.ContractedNotNull("selector");
        projector.ContractedNotNull("projector");

        return new State<TResult, TState>(s => {
            var value = @this.RunState(s).Value;
            return new State<TResult, TState>.StateTuple(
                projector(value, selector(value).RunState(s).Value) ,s);
        } );    
    }
  }

以下是 State monad 类的基本要素:

  public struct State<TValue,TState> : IEquatable<State<TValue,TState>>
  where TValue:struct where TState:struct {
    public delegate StateTuple Transformer(TState state);

    public State(Transformer transformer) : this() {
        Contract.Requires(transformer != null);
        _transformer = transformer;
    }

    [Pure]public State<TResult,TState>    Bind<TResult> (
      Func<TValue, State<TResult,TState>> selector
    ) where TResult:struct {
        selector.ContractedNotNull("selector");

        var @this = this;
        return new State<TResult,TState>(state => {
            var tuple = @this.RunState(state);
            return selector(tuple.Value).RunState(tuple.State);
        } );
    }

    [Pure]public StateTuple   RunState(TState state)  { return _transformer(state); }
    [Pure]public TValue       EvalState(TState state) { return RunState(state).Value; }
    [Pure]public TState       ExecState(TState state) { return RunState(state).State; }

    private readonly Transformer _transformer;
  }


public struct StateTuple {
  public StateTuple(Tuple<TValue, TState> content) : this(content.Item1,content.Item2) {
      content.ContractedNotNull("content");
  }
  public StateTuple(TValue value, TState state ) : this() {
      _value = value; _state = state;
  }
  public TValue Value { get {return _value;} } readonly TValue _value;
  public TState State { get {return _state;} } readonly TState _state;

  #region Value Equality with IEquatable<T>.
  /// <inheritdoc/>
  [Pure]public override bool Equals(object obj) { 
    var other = obj as StateTuple?;
    return other != null  &&  other.Equals(obj);
  }

  /// <summary>Tests value-equality, returning <b>false</b> if either value doesn't exist.</summary>
  [Pure]public bool Equals(StateTuple other) {
    return this.Value.Equals(other.Value)  &&  this.State.Equals(other.State);
  }

  /// <inheritdoc/>
  [Pure]public override int GetHashCode() { unchecked { return Value.GetHashCode() ^ State.GetHashCode(); } }

  /// <inheritdoc/>
  [Pure]public override string ToString() {
    Contract.Ensures(Contract.Result<string>() != null);
    return String.Format("({0},{1})",Value,State);
  }

  /// <summary>Tests value-equality, returning <b>false</b> if either value doesn't exist.</summary>
  [Pure]public static bool operator == (StateTuple lhs, StateTuple rhs) { return lhs.Equals(rhs); }

  /// <summary>Tests value-inequality, returning <b>false</b> if either value doesn't exist..</summary>
  [Pure]public static bool operator != (StateTuple lhs, StateTuple rhs) { return ! lhs.Equals(rhs); }
  #endregion
}

这里还有一些需要编译的实用程序:

  /// <summary>TODO</summary>
  public static class State {
    public static State<bool,TState>            DoWhile<TState>( this
        State<bool,TState>.Transformer body
    ) where TState:struct {
        return new State<bool,TState>(state => {
            State<bool,TState>.StateTuple tuple;
            do { tuple = body(state); state = tuple.State; } while (tuple.Value);
            return tuple;
        } );
    }

    /// <summary>Implements the Haskell operator (liftM): liftM f m = m >>= (\x -> return (f x))</summary>
    public static State<B,TState> LiftM<TState,A,B>( this
        State<A,TState> @this,
        Func<A,B> func
    ) where TState:struct where A:struct where B:struct {
        return @this.Bind(t => new State<B,TState>(s => new State<B,TState>.StateTuple(func(t),s)) );
    }
  }

  /// <summary>TODO</summary>
  public static class State<TState> where TState:struct {
    public readonly static State<TState, TState>    Get
        = new State<TState, TState>(s => new State<TState,TState>.StateTuple(s, s));
    public          static State<Unit,TState>       Put(TState state) {
        return new State<Unit,TState>( s => new State<Unit,TState>.StateTuple(Unit.unit,state) );
    }

    #region Convenience extensions to Get() for efficiency
    /// <summary>TODO</summary>
    /// <typeparam name="TValue"></typeparam>
    /// <typeparam name="TState"></typeparam>
    /// <param name="selector"></param>
    [Pure]public static State<TValue,TState>   GetBind<TValue>(
        Func<TState, State<TValue,TState>> selector
    ) where TValue:struct {
        selector.ContractedNotNull("selector");

        return new State<TValue,TState>( s => selector(s).RunState(s) );
    }

    /// <summary>TODO</summary>
    /// <typeparam name="TValue"></typeparam>
    /// <typeparam name="TState"></typeparam>
    /// <param name="selector"></param>
    [Pure]public static State<TValue,TState>   GetCompose<TValue>(
        Func<TState,State<TValue,TState>> selector
    ) where TValue:struct {
        selector.ContractedNotNull("selector");

        return new State<TValue,TState>( s => selector(s).RunState(s) );
    }

    /// <summary>TODO</summary>
    /// <typeparam name="TValue"></typeparam>
    /// <typeparam name="TState"></typeparam>
    /// <param name="selector"></param>
    [Pure]public static State<Unit,TState>    GetComposePut(
        Func<TState,TState> transform
    ) {
        transform.ContractedNotNull("transform");

        return new State<Unit,TState>( s => new State<Unit,TState>.StateTuple(Unit.unit,transform(s)) );
    }

    /// <summary>TODO</summary>
    /// <typeparam name="TValue"></typeparam>
    /// <typeparam name="TState"></typeparam>
    /// <param name="selector"></param>
    [Pure]public static State<TValue,TState>   GetSelect<TValue>(
        State<TValue,TState>.Transformer projector
    ) where TValue:struct {
        projector.ContractedNotNull("projector");

        return new State<TValue,TState>(projector);
    }
    #endregion
  }

【问题讨论】:

  • @Codexer 不这么认为;我现在在我的代码中有一个示例,当我用静态函数替换 lambda 时仍然无法编译。同样,当我将 lambda 中的用法作为对 s 的字段引用时。
  • 这一切都令人难以置信,但如果我可以编译它,我或许可以提供一些见解。我在任何地方都找不到 StateTuple 的声明。在构建之前继续添加声明是否安全?
  • State&lt;T, S&gt;.Get的定义在哪里?缺少很多定义来编译它。
  • @recursive:添加了StructTuple 代码。我还没有把它削减到最低限度。我相信可以添加我无意中遗漏的任何东西的假人。
  • @PieterGeerkens:感谢更新,虽然新增的内容有自己的编译问题,比如StateTuple 引用了一些未声明的泛型参数。我将做出一些有根据的假设。

标签: c# linq extension-methods monads


【解决方案1】:

我认为您对Select 的定义是问题所在。它应该有 State&lt;T, S&gt; -&gt; Func&lt;T, R&gt; -&gt; State&lt;R, S&gt; 类型,即它应该只对结果类型而不是状态进行操作。如果您添加具有该签名的实例方法:

public State<TResult, TState> Select<TResult>(Func<TValue, TResult> selector)
{
    Transformer t = this._transformer;
    var inner = new State<TResult, TState>.Transformer(s =>
    {
        var inter = t(s);
        return new State<TResult, TState>.StateTuple(selector(inter.Value), inter.State);
    });
    return new State<TResult, TState>(inner);
}

请注意,上述定义需要 State 类型的 TStateTResult 参数中的 struct 限制(这不是必需的)。

然后修正你的状态表达式的类型:

private static readonly State<TupleUnit, GCDState> GcdInner_Good =
      (from s in State<Unit, GCDState>.Get
       select new TupleUnit(Unit.unit,
             s.A > s.B ? new GCDState(s.B, s.A - s.B)
           : s.A < s.B ? new GCDState(s.B, s.A)
                       : s)
      );

        private static readonly State<TupleUnit, GCDState> GcdInner_Bad =
      (from s in State<Unit, GCDState>.Get
       let A = s.A
       let B = s.B
       select new TupleUnit(Unit.unit,
             A > B ? new GCDState(B, A - B)
           : A < B ? new GCDState(B, A)
                       : s)
      );

然后他们都会进行类型检查。

包含let 子句的查询表达式

from s in src
let x = s.Expr
select x

将转换为:

src.Select(s => new { S = S, X = s.Expr }).Select(s => s.X);

并且编译器无法推断中间 Select 调用的类型。这可能是因为它是一个扩展方法并且包含一个未使用的类型参数。如果将其设为实例方法,则可以消除这种歧义。

State 的以下实现支持在查询语法中使用let

public class State<TState, TResult>
{
    private readonly Func<TState, StateResult<TState, TResult>> f;

    public State(Func<TState, StateResult<TState, TResult>> f)
    {
        this.f = f;
    }

    public StateResult<TState, TResult> Run(TState state)
    {
        return this.f(state);
    }

    public TResult RunResult(TState state)
    {
        return this.f(state).Result;
    }

    public TState RunState(TState state)
    {
        return this.f(state).State;
    }

    public State<TState, TOut> Select<TOut>(Func<TResult, TOut> mapFunc)
    {
        Contract.Requires(mapFunc != null);

        return new State<TState, TOut>(s =>
        {
            var thisResult = this.f(s);
            return new StateResult<TState, TOut>(s, mapFunc(thisResult.Result));
        });
    }

    public State<TState, TOut> BiSelect<TOut>(Func<StateResult<TState, TResult>, StateResult<TState, TOut>> mapFunc)
    {
        return new State<TState, TOut>(s =>
        {
            return mapFunc(this.f(s));
        });
    }

    public State<TState, TOut> SelectMany<TOut>(Func<TResult, State<TState, TOut>> bindFunc)
    {
        return SelectMany(bindFunc, (_, r) => r);
    }

    public State<TState, TOut> SelectMany<TInter, TOut>(Func<TResult, State<TState, TInter>> bindFunc, Func<TResult, TInter, TOut> selector)
    {
        return new State<TState, TOut>(initialState =>
        {
            var thisResult = this.f(initialState);
            var nextState = bindFunc(thisResult.Result);
            var nextResult = nextState.Run(thisResult.State);
            var result = selector(thisResult.Result, nextResult.Result);
            return new StateResult<TState, TOut>(nextResult.State, result);
        });
    }
}

public static class State
{
    public static State<TState, TResult> FromResult<TState, TResult>(TResult result)
    {
        return new State<TState, TResult>(s => new StateResult<TState, TResult>(s, result));
    }

    public static State<TState, TState> Get<TState>()
    {
        return new State<TState, TState>(s => new StateResult<TState, TState>(s, s));
    }

    public static State<TState, Unit> Put<TState>(TState state)
    {
        return new State<TState, Unit>(_ => new StateResult<TState, Unit>(state, Unit.Instance));
    }

    public static State<TState, Unit> Modify<TState>(Func<TState, TState> modifyFunc)
    {
        return from s in Get<TState>()
               from _ in Put(modifyFunc(s))
               select Unit.Instance;
    }
}

public struct StateResult<TState, TResult>
{
    public StateResult(TState state, TResult result)
        : this()
    {
        this.State = state;
        this.Result = result;
    }

    public TState State { get; private set; }
    public TResult Result { get; private set; }
}

【讨论】:

  • 有趣的思路;但我无法(至少)确定如何使用该类型签名为 Select 实现 State monad 语义。我最初设置了与此类似的所有三种扩展方法,但必须转向我现在必须获得 fromselect 子句以供 LINQ 编译和运行;这似乎是一种回归,但我今晚会追得更远。
  • @PieterGeerkens - 我用Select 的实现更新了答案。
  • 您的一些细节不正确,特别是现有的Select 在其他地方用于 LINQ,但需要使用此签名进行第二次实现:public static State&lt;TResult,TState&gt; Select&lt;TValue,TState,TResult&gt;( this State&lt;TValue,TState&gt; @this, Func&lt;TValue, TResult&gt; projector )。然而,新的给了我一个新的错误:“类型'AnonymousType#1'必须是一个不可为空的值类型才能将它用作泛型类型或方法中的参数TResult'...... Select(.. .System.Func)'".
  • 如果您没有收到该错误,可能是我仍在使用 VS 2013 和 Framework 4.5。这是进步,但我现在可能不得不忍受无法使用let
  • @PieterGeerkens - 抱歉,您不需要在 Select 中对 TResult 使用 struct 约束,请参阅更新。您还需要删除State 定义中TStateTValue 的结构约束。
猜你喜欢
  • 1970-01-01
  • 2014-04-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-10-11
  • 1970-01-01
  • 2020-11-30
相关资源
最近更新 更多