How to interweave state with list #917
Replies: 7 comments
-
First make your public static class InterpretCommands
{
public static Either<Error, (SpecificPoint Current, Seq<SpecificPoint> Points)> InterpretCmds(SpecificPoint specificPoint, Cmd cmd) => cmd switch
{
PenUp pup => from r in PenUpHandler(pup, specificPoint)
select (r, Seq1(r)),
PenDown pdown => from r in PenDownHandler(pdown, specificPoint)
select (r, Seq1(r)),
StrokeSize size => from r in StrokeSizeHandler(size, specificPoint)
select (r, Seq1(r)),
Move move => MoveHandler(move, specificPoint),
_ => Error.New("Invalid operation")
};
public static Either<Error, SpecificPoint> PenUpHandler(PenUp cmd, SpecificPoint specPoint) =>
specPoint.With(Draw: false);
public static Either<Error, SpecificPoint> PenDownHandler(PenDown cmd, SpecificPoint specPoint) =>
specPoint.With(Draw: true);
public static Either<Error, SpecificPoint> StrokeSizeHandler(StrokeSize cmd, SpecificPoint specificPoint) =>
specificPoint.With(BrushSize: cmd.Size);
public static Either<Error, (SpecificPoint, Seq<SpecificPoint>)> MoveHandler(Move cmd, SpecificPoint specificPoint)
{
var curX = (int)specificPoint.Point.X;
var curY = (int)specificPoint.Point.Y;
var paces = cmd.Paces;
return cmd.Direction switch
{
Directions.South => Interpolate(specificPoint, new Point(curX, curY + paces)),
Directions.East => Interpolate(specificPoint, new Point(curX + paces, curY)),
Directions.North => Interpolate(specificPoint, new Point(curX, curY - paces)),
_ => Interpolate(specificPoint, new Point(curX - paces, curY))
};
}
static double MakeUnit(double x) =>
x < 0 ? -1
: x > 0 ? 1
: 0;
static Either<Error, (SpecificPoint, Seq<SpecificPoint>)> Interpolate(SpecificPoint origin, Point destination)
{
var dx = MakeUnit(destination.X - origin.Point.X);
var dy = MakeUnit(destination.Y - origin.Point.Y);
var current = new Point(origin.Point.X, origin.Point.Y);
IEnumerable<Either<Error, SpecificPoint>> Yield()
{
while(current.X != destination.X || current.Y != destination.Y)
{
current.X += dx;
current.Y += dy;
yield return current.X < 0 ? Left(Error.New("X out of bounds"))
: current.Y < 0 ? Left(Error.New("Y out of bounds"))
: Right<Error, SpecificPoint>(origin.With(Point: new Point(current.X, current.Y)));
}
}
return from pts in Yield().Sequence()
select (origin.With(Point: destination), pts.ToSeq().Strict());
}
} I've done some refactoring to use C#8 switch expressions rather than the old switch statements. I've also broken out the Next in public delegate Either<Error, Seq<SpecificPoint>> GeneratePoints(Seq<Cmd> cmd);
public delegate Either<Error, Seq<Cmd>> ParseCmdsDel(string text);
public delegate Either<Error, Unit> WriteDel(Seq<SpecificPoint> seqs);
public delegate Either<Error, string> ReadDel(string path); Replace the delegates assignment with: ParseCmdsDel parseCommands = ParseCommands;
WriteDel writePoints = WritePoints;
Either<Error, string> ReadText(string path) =>
Try(() => ReadAllText(path)).ToEither().MapLeft(Error.New); That wraps up the file-reading, so that any exceptions get converted into the common
Either<Error, Seq<SpecificPoint>> GeneratePoints(Seq<Cmd> cmds) =>
cmds.FoldWhile(InitialState,
(state, cmd) => from s in state
from r in InterpretCmds(s.Current, cmd)
select (r.Current, s.Points + r.Points),
state => state.IsRight)
.Map(state => state.Points); It takes an initial state: static Either<Error, (SpecificPoint Current, Seq<SpecificPoint> Points)> InitialState =>
Right((SpecificPoint.Default, Seq<SpecificPoint>())); Finally, public Func<string, Either<Error, Unit>> Run(
ReadDel readAllText,
ParseCmdsDel parseCommands,
WriteDel writePoints,
GeneratePoints generatePoints) =>
path =>
from text in readAllText(path)
from cmds in parseCommands(text)
from pts in generatePoints(cmds)
from resu in writePoints(pts)
select resu; Notice how the result is a var runner = Run(ReadText, parseCommands, writePoints, GeneratePoints); It can then be called with the injected path name: var result = runner("Instructions.txt"); Other things to note:
I have posted my fully working code to: https://gist.github.com/louthy/a09f660b1b2543d1784758eb2acf7504 |
Beta Was this translation helpful? Give feedback.
-
Awesome. I was just looking for some direction, but this really helps. The FoldWhile is very cool. I was wondering what the reason for the Strict() call in InterpretCommands. I see that without the call we just sit at the line The reusable runner is nice too. I will try to get a bunch of files drawing. |
Beta Was this translation helpful? Give feedback.
-
|
Beta Was this translation helpful? Give feedback.
-
I probably should have opened a separate issue but I wanted to keep the context together, I changed the main method of the program to exploit using the reusable runner from above. Now the program takes multiple instruction files and run them using apply so that if one fails the others keep writing. Anway, the following code works
` However if I change the declaration and setting of fileNames to the following then nothing runs. |
Beta Was this translation helpful? Give feedback.
-
Use: var fileNames = Seq("Instructions.txt", "Instructions0.txt", "Instructions2.txt"); The other form of construction I think calls |
Beta Was this translation helpful? Give feedback.
-
Following up on the collection initialisation approach used. It appears that it's a limitation of C# that any I have raised the issue on the csharplang github repo. But, for now, just consider it to not work and construct with the constructor function I mention above. |
Beta Was this translation helpful? Give feedback.
-
Will do. Thanks for looking into this! |
Beta Was this translation helpful? Give feedback.
-
The following exercise (from Pragmatic Programmer) reads instructions from a text file and draws the results on a canvas. The authors suggest using Yacc but I thought it would be fun to try it with parsec in Language-Ext and that part was a breeze. But I ran into some problems generating the points to draw to the screen in a functional manner and was looking for some assistance in this area.
The commands in instruction.txt are
D
P 5
S 2
S 10
E 30
N 5
W 2
U
D = pen down on canvas
P n = set brush stroke size to n
S n = south n paces
E n = east n paces
N n = up n paces
W n = left n paces
U = pen up off canvas
Finally these 3 extension methods are my various attempts at functionally generating points by taking an initial default specificPoint state of (x =0, y =, pendown = false, brushsize =2). This state is combined with the current list element which creates a new starting state for the next element and also creates a new list, as the current new element, that get flatten later. I am currently using the first imperative approach with mutation, but I want to know if there is a better way to do this?
Also what if my InterpetCmds method had to handle out of bounds points? For example if the instructions say to move left beyond the y axis like y = -1. I would like to return an either in this case. And have the whole process stop? How could I get this to work in my mapAccuml or some other functional technique?
So if my signature is like this
public static Either<string, (SpecificPoint, Seq<SpecificPoint>)> InterpretCmds(SpecificPoint specificPoint, Cmd cmd)
, is there a way to have the mapAccuml or some other method short circuit? In haskell is the left side of the mapAccuml tuple evaluated lazily and does that make using traverse on that list more effecient than is possible in C#?Beta Was this translation helpful? Give feedback.
All reactions