Skip to content

Commit

Permalink
Merge pull request #661 from sys27/feature/function-closure
Browse files Browse the repository at this point in the history
#653 - Add support of function currying.
  • Loading branch information
sys27 committed Aug 1, 2023
2 parents 65e2d11 + 392a46b commit 1fae7e7
Show file tree
Hide file tree
Showing 9 changed files with 199 additions and 94 deletions.
2 changes: 1 addition & 1 deletion xFunc.DotnetTool/xFunc.DotnetTool.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<TargetFrameworks>net6.0;net7.0</TargetFrameworks>
<LangVersion>latest</LangVersion>
<PackAsTool>true</PackAsTool>
<ToolCommandName>xFunc</ToolCommandName>
<ToolCommandName>xfunc</ToolCommandName>
<PackageId>xFunc.DotnetTool</PackageId>
<Version>4.2.0</Version>
<Product>xFunc.Maths</Product>
Expand Down
2 changes: 1 addition & 1 deletion xFunc.Maths/Expressions/CallExpression.cs
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ public object Execute(ExpressionParameters? parameters)
if (Function.Execute(parameters) is not Lambda function)
throw new ResultIsNotSupportedException(this, Function);

var nestedScope = parameters.CreateScope();
var nestedScope = ExpressionParameters.CreateScoped(function.CapturedScope ?? parameters);
var zip = function.Parameters.Zip(Parameters, (parameter, expression) => (parameter, expression));
foreach (var (parameter, expression) in zip)
nestedScope[parameter] = new ParameterValue(expression.Execute(nestedScope));
Expand Down
29 changes: 24 additions & 5 deletions xFunc.Maths/Expressions/Lambda.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ namespace xFunc.Maths.Expressions;
/// </summary>
/// <param name="body">The body of the function.</param>
public Lambda(IExpression body)
: this(ImmutableArray.Create<string>(), body)
: this(ImmutableArray.Create<string>(), body, null)
{
}

Expand All @@ -26,7 +26,7 @@ public Lambda(IExpression body)
/// <param name="parameters">The list of parameters of the function.</param>
/// <param name="body">The body of the function.</param>
public Lambda(IEnumerable<string> parameters, IExpression body)
: this(parameters.ToImmutableArray(), body)
: this(parameters.ToImmutableArray(), body, null)
{
}

Expand All @@ -36,9 +36,15 @@ public Lambda(IEnumerable<string> parameters, IExpression body)
/// <param name="parameters">The list of parameters of the function.</param>
/// <param name="body">The body of the function.</param>
public Lambda(ImmutableArray<string> parameters, IExpression body)
: this(parameters, body, null)
{
}

private Lambda(ImmutableArray<string> parameters, IExpression body, ExpressionParameters? capturedScope)
{
Parameters = parameters;
Body = body;
CapturedScope = capturedScope;
}

/// <summary>
Expand Down Expand Up @@ -80,10 +86,18 @@ public override string ToString()
/// <summary>
/// Calls the function.
/// </summary>
/// <param name="expressionParameters">An object that contains all parameters and functions for expressions.</param>
/// <param name="parameters">An object that contains all parameters and functions for expressions.</param>
/// <returns>A result of the execution.</returns>
public object Call(ExpressionParameters expressionParameters)
=> Body.Execute(expressionParameters);
public object Call(ExpressionParameters parameters)
=> Body.Execute(parameters);

/// <summary>
/// Returns a new lambda instance with captured parameters.
/// </summary>
/// <param name="parameters">An object that contains all parameters and functions for expressions.</param>
/// <returns>The lambda with captured parameters.</returns>
public Lambda Capture(ExpressionParameters? parameters)
=> new Lambda(Parameters, Body, parameters);

/// <summary>
/// Converts <see cref="Lambda"/> to <see cref="LambdaExpression"/>.
Expand All @@ -101,4 +115,9 @@ public LambdaExpression AsExpression()
/// Gets an expression of the function body.
/// </summary>
public IExpression Body { get; }

/// <summary>
/// Gets the captured scope.
/// </summary>
internal ExpressionParameters? CapturedScope { get; }
}
2 changes: 1 addition & 1 deletion xFunc.Maths/Expressions/LambdaExpression.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ public object Execute()

/// <inheritdoc />
public object Execute(ExpressionParameters? parameters)
=> Lambda;
=> Lambda.Capture(parameters);

/// <inheritdoc />
public string ToString(IFormatter formatter)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// Copyright (c) Dmytro Kyshchenko. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

namespace xFunc.Maths.Expressions.Parameters;

/// <summary>
/// Strongly typed collection that contains parameters.
/// </summary>
public partial class ExpressionParameters
{
private sealed class ScopedExpressionParameters : ExpressionParameters
{
private readonly ExpressionParameters parent;

public ScopedExpressionParameters(ExpressionParameters parent)
: base(false)
=> this.parent = parent;

public override IEnumerator<Parameter> GetEnumerator()
{
foreach (var parameter in parent)
yield return parameter;

foreach (var (_, parameter) in collection)
yield return parameter;
}

public override ParameterValue this[string key]
{
get => collection.TryGetValue(key, out var parameter)
? parameter.Value
: parent[key];
set
{
if (collection.TryGetValue(key, out _))
base[key] = value;
else if (parent.TryGetParameter(key, out _))
parent[key] = value;
else
base[key] = value;
}
}

public override bool Contains(Parameter param)
=> base.Contains(param) || parent.Contains(param);

public override bool ContainsKey(string key)
=> base.ContainsKey(key) || parent.ContainsKey(key);
}
}
82 changes: 35 additions & 47 deletions xFunc.Maths/Expressions/Parameters/ExpressionParameters.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,16 @@

using System.Collections;
using System.Collections.Specialized;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Numerics;

namespace xFunc.Maths.Expressions.Parameters;

/// <summary>
/// Strongly typed dictionary that contains value of variables.
/// Strongly typed collection that contains parameters.
/// </summary>
public class ExpressionParameters : IEnumerable<Parameter>, INotifyCollectionChanged
public partial class ExpressionParameters : IEnumerable<Parameter>, INotifyCollectionChanged
{
private static readonly Dictionary<string, Parameter> Constants;
private readonly Dictionary<string, Parameter> collection;
Expand Down Expand Up @@ -276,14 +277,35 @@ public virtual IEnumerator<Parameter> GetEnumerator()
}

private Parameter GetParameterByKey(string key)
{
if (TryGetParameter(key, out var parameter))
return parameter;

throw new KeyNotFoundException(string.Format(CultureInfo.InvariantCulture, Resource.VariableNotFoundExceptionError, key));
}

/// <summary>
/// Gets the value of parameter.
/// </summary>
/// <param name="key">The name of parameter.</param>
/// <param name="parameter">The parameter.</param>
/// <returns><c>true</c> if the current collection contains specified parameter, otherwise <c>false</c>.</returns>
public bool TryGetParameter(string key, [NotNullWhen(true)] out Parameter? parameter)
{
if (collection.TryGetValue(key, out var param))
return param;
{
parameter = param;
return true;
}

if (withConstants && Constants.TryGetValue(key, out param))
return param;
{
parameter = param;
return true;
}

throw new KeyNotFoundException(string.Format(CultureInfo.InvariantCulture, Resource.VariableNotFoundExceptionError, key));
parameter = null;
return false;
}

/// <summary>
Expand Down Expand Up @@ -318,15 +340,18 @@ public void Add(string key, ParameterValue value)
/// <param name="param">The element.</param>
/// <exception cref="ArgumentNullException"><paramref name="param"/> is null.</exception>
/// <exception cref="ParameterIsReadOnlyException">The variable is read only.</exception>
public void Remove(Parameter param)
/// <returns><c>true</c> if item was successfully removed from the collection; otherwise, <c>false</c>.</returns>
public bool Remove(Parameter param)
{
if (param is null)
throw new ArgumentNullException(nameof(param));
if (param.Type == ParameterType.Constant)
throw new ArgumentException(Resource.ConstError);

collection.Remove(param.Key);
var result = collection.Remove(param.Key);
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, param));

return result;
}

/// <summary>
Expand Down Expand Up @@ -376,45 +401,8 @@ public virtual bool ContainsKey(string key)
/// <summary>
/// Creates a new nested scope of parameters.
/// </summary>
/// <param name="parameters">The instance of the base parameters collection.</param>
/// <returns>The expression parameters.</returns>
public ExpressionParameters CreateScope()
=> new ScopedExpressionParameters(withConstants, this);

private sealed class ScopedExpressionParameters : ExpressionParameters
{
private readonly ExpressionParameters parent;

public ScopedExpressionParameters(bool initConstants, ExpressionParameters parent)
: base(initConstants)
=> this.parent = parent;

public override IEnumerator<Parameter> GetEnumerator()
{
foreach (var parameter in parent)
yield return parameter;

foreach (var (_, parameter) in collection)
yield return parameter;
}

public override ParameterValue this[string key]
{
get
{
if (collection.TryGetValue(key, out var parameter))
{
return parameter.Value;
}

return parent[key];
}
set => base[key] = value;
}

public override bool Contains(Parameter param)
=> base.Contains(param) || parent.Contains(param);

public override bool ContainsKey(string key)
=> base.ContainsKey(key) || parent.ContainsKey(key);
}
public static ExpressionParameters CreateScoped(ExpressionParameters parameters)
=> new ScopedExpressionParameters(parameters);
}
20 changes: 1 addition & 19 deletions xFunc.Maths/Processor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -87,15 +87,7 @@ public Processor()
/// </summary>
/// <param name="function">The function.</param>
/// <returns>The result of solving.</returns>
public IResult Solve(string function) => Solve(function, true);

/// <summary>
/// Solves the specified expression.
/// </summary>
/// <param name="function">The function.</param>
/// <param name="simplify">if set to <c>true</c> parser will simplify expression.</param>
/// <returns>The result of solving.</returns>
public IResult Solve(string function, bool simplify)
public IResult Solve(string function)
{
var exp = Parse(function);
exp.Analyze(typeAnalyzer);
Expand Down Expand Up @@ -161,16 +153,6 @@ MatrixValue matrixValue
public TResult Solve<TResult>(string function) where TResult : IResult
=> (TResult)Solve(function);

/// <summary>
/// Solves the specified function.
/// </summary>
/// <typeparam name="TResult">The type of the result.</typeparam>
/// <param name="function">The function.</param>
/// <param name="simplify">if set to <c>true</c> parser will simplify expression.</param>
/// <returns>The result of solving.</returns>
public TResult Solve<TResult>(string function, bool simplify) where TResult : IResult
=> (TResult)Solve(function, simplify);

/// <summary>
/// Simplifies the <paramref name="function"/>.
/// </summary>
Expand Down

0 comments on commit 1fae7e7

Please sign in to comment.