Expression problem: Difference between revisions

Content deleted Content added
Line 124:
 
=== Problem description ===
We can imagine we do not have the source code for the following library, written in [[C Sharp (programming language)|C#]], which we wish to extend:

<syntaxhighlight lang="c#" line="1">
public interface IEvalExp
{
int Eval();
}
 
public class Lit : IEvalExp
{
public Lit(int n)
Line 135 ⟶ 138:
N = n;
}
 
public int N { get; }
 
public int Eval()
{
Line 141 ⟶ 146:
}
}
 
public class Add : IEvalExp
{
public Add(IEvalExp left, IEvalExp right)
Line 148 ⟶ 154:
Right = right;
}
 
public IEvalExp Left { get; }
 
public IEvalExp Right { get; }
 
public int Eval()
{
Line 159 ⟶ 168:
{
public static IEvalExp AddOneAndTwo() => new Add(new Lit(1), new Lit(2));
 
public static int EvaluateTheSumOfOneAndTwo() => AddOneAndTwo().Eval();
}
</syntaxhighlight>
</syntaxhighlight>Using this library we can express the arithmetic expression ''1 + 2'' as we did in ''ExampleOne.AddOneAndTwo()'' and can evaluate the expression by calling ''.Eval()''. Now imagine that we wish to extend this library, adding a new type is easy because we are working with an [[Object-oriented programming|Object-oriented programming language]]. For example, we might create the following class:<syntaxhighlight lang="c#" line="1">
public class Mult : IEvalExp
 
</syntaxhighlight>Using this library we can express the arithmetic expression ''1 + 2'' as we did in ''ExampleOne.AddOneAndTwo()'' and can evaluate the expression by calling ''.Eval()''. Now imagine that we wish to extend this library, adding a new type is easy because we are working with an [[Object-oriented programming|Object-oriented programming language]]. For example, we might create the following class:<syntaxhighlight lang="c#" line="1">
 
<syntaxhighlight lang="c#" line="1">
public class Mult : IEvalExp
public Mult(IEvalExp left, IEvalExp right)
{
Line 173 ⟶ 184:
 
public IEvalExp Left { get; }
 
public IEvalExp Right { get; }
 
Line 180 ⟶ 192:
}
}
</syntaxhighlight>
</syntaxhighlight>However, if we wish to add a new function over the type (a new method in C# terminology) we have to change the ''IEvalExp'' interface and then modify all the classes that implement the interface. Another possibility is to create a new interface that extends the ''IEvalExp'' interface and then create sub-types for ''Lit'', ''Add'' and ''Mult'' classes, but the expression returned in ''ExampleOne.AddOneAndTwo()'' has already been compiled so we will not be able to use the new function over the old type. The problem is reversed in functional programming languages like [[F Sharp (programming language)|F#]] where it is easy to add a function over a given type, but extending or adding types is difficult.
 
</syntaxhighlight>However, if we wish to add a new function over the type (a new method in C# terminology) we have to change the ''IEvalExp'' interface and then modify all the classes that implement the interface. Another possibility is to create a new interface that extends the ''IEvalExp'' interface and then create sub-types for ''Lit'', ''Add'' and ''Mult'' classes, but the expression returned in ''ExampleOne.AddOneAndTwo()'' has already been compiled so we will not be able to use the new function over the old type. The problem is reversed in functional programming languages like [[F Sharp (programming language)|F#]] where it is easy to add a function over a given type, but extending or adding types is difficult.
 
=== Problem Solution using Object Algebra ===
Let us redesign the original library with extensibility in mind using the ideas from the paper ''Extensibility for the Masses.''<ref name="Oliveira & Cook, Object Algebras" />

<syntaxhighlight lang="c#" line="1">
public interface ExpAlgebra<T>
{
Line 190 ⟶ 206:
}
 
public class ExpFactory : ExpAlgebra<IEvalExp>
{
public IEvalExp AddLit(IEvalExpint left, IEvalExp rightn)
{
return new AddLit(left, rightn);
}
 
public IEvalExp LitAdd(intIEvalExp nleft, IEvalExp right)
{
return new LitAdd(nleft, right);
}
}
Line 207 ⟶ 223:
public static T AddOneToTwo(ExpAlgebra<T> ae) => ae.Add(ae.Lit(1), ae.Lit(2));
}
</syntaxhighlight>
</syntaxhighlight>We use the same implementation as in the first code example but now add a new interface containing the functions over the type as well as a factory for the algebra. Notice that we now generate the expression in ''ExampleTwo.AddOneToTwo()'' using the ''ExpAlgebra<T>'' interface instead of directly from the types. We can now add a function by extending the ''ExpAlgebra<T>'' interface, we will add functionality to print the expression:<syntaxhighlight lang="c#" line="1">
 
public interface IPrintExp : IEvalExp
</syntaxhighlight>We use the same implementation as in the first code example but now add a new interface containing the functions over the type as well as a factory for the algebra. Notice that we now generate the expression in ''ExampleTwo.AddOneToTwo()'' using the ''ExpAlgebra<T>'' interface instead of directly from the types. We can now add a function by extending the ''ExpAlgebra<T>'' interface, we will add functionality to print the expression:<syntaxhighlight lang="c#" line="1">
 
<syntaxhighlight lang="c#" line="1">
public interface IPrintExp : IEvalExp
{
public string Print();
}
 
public class PrintableLit: Lit, IPrintExp
{
public PrintableLit(int n) : base(n)
{
stringN Print()= n;
}
 
public classint PrintableLitN :{ Lit,get; IPrintExp}
{
public PrintableLit(int n) : base(n)
{
N = n;
}
 
public int N { get;string }Print()
{
 
publicreturn string PrintN.ToString();
{
return N.ToString();
}
}
}
 
public class PrintableAdd : Add, IPrintExp
{
public PrintableAdd(IPrintExp left, IPrintExp right) : base(left, right)
{
NLeft = nleft;
public PrintableAdd(IPrintExp left, IPrintExp right) : base(left, right)
{Right = right;
{}
Left = left;
Right = right;
}
 
public new IPrintExp Left { get; }
public new IPrintExp Right { get; }
 
public new IPrintExp Right { get; }
public string Print()
{
return Left.Print() + " + " + Right.Print();
}
}
 
public string Print()
public class PrintFactory : ExpFactory, ExpAlgebra<IPrintExp>
{
return Left.Print() + " + " + Right.Print();
public IPrintExp Add(IPrintExp left, IPrintExp right)
{}
}
return new PrintableAdd(left, right);
}
 
public class PrintFactory : ExpFactory, ExpAlgebra<IPrintExp>
public new IPrintExp Lit(int n)
{
{
public IPrintExp Add(IPrintExp left, IPrintExp right)
return new PrintableLit(n);
}{
return new PrintableAdd(left, right);
}
 
public staticnew classIPrintExp ExampleThreeLit(int n)
{
return new PrintableLit(n);
public static int Evaluate() => ExampleTwo<IPrintExp>.AddOneToTwo(new PrintFactory()).Eval();
public static string Print() => ExampleTwo<IPrintExp>.AddOneToTwo(new PrintFactory()).Print();
}
}
</syntaxhighlight>Notice that in ''ExampleThree.Print()'' we are printing an expression that was already compiled in ''ExampleTwo'', we did not need to modify any existing code. Notice also that this is still strongly typed, we do not need reflection or casting. If we would replace the ''PrintFactory()'' with the ''ExpFactory()'' in the ''ExampleThree.Print()'' we would get a compilation error since the ''.Print()'' method does not exist in that context.
 
public static class ExampleThree
{
public static int Evaluate() => ExampleTwo<IPrintExp>.AddOneToTwo(new PrintFactory()).Eval();
public static string Print() => ExampleTwo<IPrintExp>.AddOneToTwo(new PrintFactory()).Print();
}
</syntaxhighlight>
 
</syntaxhighlight>Notice that in ''ExampleThree.Print()'' we are printing an expression that was already compiled in ''ExampleTwo'', we did not need to modify any existing code. Notice also that this is still strongly typed, we do not need reflection or casting. If we would replace the ''PrintFactory()'' with the ''ExpFactory()'' in the ''ExampleThree.Print()'' we would get a compilation error since the ''.Print()'' method does not exist in that context.
 
==See also==