8
\$\begingroup\$

I have started putting together a functional programming library in C# which is somewhat inspired by scalaz and the book Functional Programming in Scala.

I am hoping to get some feedback on the Monad Transformers implementations and comments.

Please understand this is implemented in C# and .NET, which does not support typeclasses and Higher Kindedness.

Here is an example demo of what I have been able to create so far with what has been developed.

using NUnit.Framework; using System; using System.Collections.Generic; using System.Threading; using System.Linq; namespace Sharper.Tests { internal class Student { public int Id{ get; set; } public string Name{ get; set; } } internal class Score { public int StudentId{ get; set; } public string Name{ get; set; } public Decimal Result{ get; set; } } internal class ScoreServer { public ScoreServer() { students = new Dictionary<int, Student>(); scores = new Dictionary<int, List<Score>>(); students.Add(1, new Student{ Id = 1, Name = "Blair" }); students.Add(2, new Student{ Id = 2, Name = "Esther" }); scores.Add(1, new List<Score> { new Score{ StudentId = 1, Name = "Maths", Result = 76 }, new Score{ StudentId = 1, Name = "English", Result = 56 }, new Score{ StudentId = 1, Name = "Science", Result = 67 } }); scores.Add(2, new List<Score> { new Score{ StudentId = 2, Name = "Maths", Result = 87 }, new Score{ StudentId = 2, Name = "English", Result = 72 }, new Score{ StudentId = 2, Name = "Science", Result = 59 } }); } public IO<String> GetStudentScoreFor(int studentId, string classname) { var studentT = GetStudentById(studentId).OptionT(); var studentNameT = studentT.Map(x => x.Name) .OrElse(new Some<string>(studentId.ToString())); var matchedScoreT = from student in studentT from score in GetClassScoreByStudent(student.ToOption(), classname).OptionT() select String.Format("Student: {0} Recieved {1} for {2}", student.Name, score.Name, score.Result); var result = from name in studentNameT from text in matchedScoreT.Map(score => score) .OrElse(String.Format("Student {0} has not record for {1}", name, classname) .ToOption()) select text; return result.GetValueOrDefault(String.Empty); } public IO<Option<Student>> GetStudentById(int studentId) { return new IO<Option<Student>>( () => { Thread.Sleep(TimeSpan.FromSeconds(1.5)); // Simulate work return students.SafeGet(studentId); } ); } public IO<Option<Score>> GetClassScoreByStudent(Option<Student> student, string @class) { return new IO<Option<Score>>( () => { Thread.Sleep(TimeSpan.FromSeconds(1)); // Simulate work return student.FlatMap(x => scores.SafeGet(x.Id) .FlatMap(y => y.FirstOrDefault(z => z.Name == @class) .ToOption())); } ); } private Dictionary<int,Student> students{ get; set; } private Dictionary<int, List<Score>> scores { get; set; } } [TestFixture] public class OptionIODemoTests { [Test] public void Testing_ScoreServer1() { var server = new ScoreServer(); var result = server.GetStudentScoreFor(1, "Maths").PerformUnsafeIO(); Assert.AreEqual(String.Format("Student: {0} Recieved {1} for {2}", "Blair", "Maths", 76), result); } [Test] public void Testing_ScoreServer2() { var server = new ScoreServer(); var result = server.GetStudentScoreFor(1, "Mathematics").PerformUnsafeIO(); Assert.AreEqual(String.Format("Student {0} has not record for {1}", "Blair", "Mathematics"), result); } [Test] public void Testing_ScoreServer3() { var server = new ScoreServer(); var result = server.GetStudentScoreFor(3, "Mathematics").PerformUnsafeIO(); Assert.AreEqual(String.Format("Student {0} has not record for {1}", "3", "Mathematics"), result); } } } 

The main projects site is here.

\$\endgroup\$
4
  • 5
    \$\begingroup\$Just to be clear, the code that you posted in the question is what's going to get reviewed, not the whole repo. Is that what you want?\$\endgroup\$CommentedJun 22, 2015 at 15:11
  • \$\begingroup\$Looking for feedback on the posted code plus general feedback on the library.\$\endgroup\$CommentedJun 23, 2015 at 6:41
  • 3
    \$\begingroup\$We can only review the code you post here. Mostly because links rot and if you would suddenly pull your repo from the net, answers wouldn't make any sense. Wouldn't you be more interested in having your library code reviewed?\$\endgroup\$CommentedJun 23, 2015 at 20:44
  • \$\begingroup\$Have you thought of using F#? It unfortunately still lacks higher-kinded types and typeclasses, but the language is actually designed to be a functional language, based off the ML family.\$\endgroup\$
    – Dan Lyons
    CommentedJul 2, 2015 at 17:56

1 Answer 1

7
\$\begingroup\$

First, this Dictionary caught my eye:

students = new Dictionary<int, Student>(); students.Add(1, new Student{ Id = 1, Name = "Blair" }); students.Add(2, new Student{ Id = 2, Name = "Esther" }); 

So, you have a dictionary where the key is already included in the ID if the value? Why not make that a plain Dictionary<int, string>? If the keys are guaranteed to not be duplicates anyway, you could set up a List<Student> for simplicity.

However, looking beyond that, it appears we have to keep a set of Scores for each student, and you are doing that by keeping two lists. I would combine these into one list with a class Student with an Id, Name, and a List of Scores. Then, you can keep a list of students and their scores all together. This, however, has the trouble of allowing duplicate Ids, so perhaps a Dictionary is the best option here, even if the Id is in both the key and value.


Testing_ScoreServer1() isn't very descriptive. I have to read the test to figure out what it is actually testing. Later on when you make changes that make this test fail, how are you going to know what is actually failing without reading the code? I would give it a name so descriptive that I could tell what failed from the list of failed tests.


new Score{ StudentId = 1, Name = "Maths", Result = 76 }, new Score{ StudentId = 1, Name = "English", Result = 56 }, new Score{ StudentId = 1, Name = "Science", Result = 67 } 

You use the value 76 in your tests, as well as the value "Blair". What if you decide you don't like the number 76 and you change it to 75? That will break your tests. You should consider setting this value in one place as a variable so you just need to change the value in one place if it changes. Perhaps you could have a List of students that you take data from and use for all your tests?

\$\endgroup\$
0

    Start asking to get answers

    Find the answer to your question by asking.

    Ask question

    Explore related questions

    See similar questions with these tags.