Hello,
I'm still learning how to use this amazing library (and FP as well, so please go easy on me!), and am a bit confused as to how I use Try<T>. I have a very OO/imperative code base, and am trying to introduce FP where I can.
For example, I have a sequence of statements like this...
ticket.FseTravelType = GetAnswer(PrimaryTravelTypeQuestionID);
Doesn't really matter what this does, the main point is that is returns as string, but could throw an exception. Without FP, I would presumably have to do something like this...
try {
ticket.FseTravelType = GetAnswer(PrimaryTravelTypeQuestionID);
} catch (Exception ex) {
Log.Error(ex.Message);
}
However, given that I have quite a few very similar lines, I would like to cut down the boilerplate code. Try<T> looks like the answer, as (I think) should be able to write something like this...
GetAnswer(PrimaryTravelTypeQuestionID).Match(
s => ticket.FseTravelType = s,
e => Log.Error(e));
However, I can't work out how to do this. I looked at the unit tests, but couldn't work out how to translate what I saw there into this.
Anyone able to help me out here? Sorry if this is a dumb question, but as I said, I'm fairly new to FP, so am still a bit vague about how it works.
Thanks
After adding using static LanguageExt.Prelude; to the top of your file, I think you want
Try(() => GetAnswer(PrimaryTravelTypeQuestionID))
.Match(
s => ticket.FseTravelType = s,
e => Log.Error(e));
@bender2k14 That's absolutely brilliant, thanks a lot. I'll get there in the end :+1:
Anyone able to help me out here? Sorry if this is a dumb question, but as I said, I'm fairly new to FP, so am still a bit vague about how it works.
One thing I'd suggest is trying to stick to a few golden rules:
Composition is the name of the game. By writing composable functions that have no side-effects you make it easier to create higher level abstractions. The example you gave above may be using types from this function library, but you're doing it in a very imperative way.
So, this:
```c#
Try(() => GetAnswer(PrimaryTravelTypeQuestionID))
.Match(
s => ticket.FseTravelType = s,
e => Log.Error(e));
Has:
* External dependencies (`Log.Error(e)`)
* Causes side-effects
* `Log.Error(e)` affects the _world_
* `GetAnswer(PrimaryTravelTypeQuestionID)` presumably is talking to a database or something like that? Either way, it can't get an answer from an ID without relying on some global state.
Now, it's obviously not always possible to not cause side-effects, especailly using C# as a language. But we can improve the situation by:
* Moving IO to the _edges_. This means do your IO first and last, and have pure code in-between. This at least silos the dangerous stuff, and makes it easier for us to build higher abstractions.
* Using monadic types to abstract away from the messy details
`GetAnswer` is a good example of something we can improve with a bit of cunning:
Imagine this:
```c#
static string log = "";
static Map<int, string> answers;
string GetAnswer(int id)
{
var answer = answers[id];
log = log + $"Got answer {id}";
return answer;
}
When we write it like this, it becomes really clear what the external dependencies are. So, how do we improve the situation? Well, we want the function to only depend on it's arguments, not some global state:
```c#
(string answer, string log) GetAnswer(int id, Map
{
var answer = answers[id];
log = log + $"Got answer {id}";
return (answer, log);
}
So, now we're passing in all the possible answers, and returning the chosen `answer` and the updated `log`.
But, we still have some problems (apart from lots of argument typing). And it's this:
```c#
log = log + $"Got answer {id}";
Why should this function know anything about _how_ to log? the + operation is the mechanics of logging, we shouldn't know about it. And, wow many functions are we going to have to write this in? What happens when we change our minds? Could this function die if a log file gets full? Are there any performance impacts to this?
All of these things makes the manually written log code bad.
So, let's change it slightly:
```c#
(string answer, string log) GetAnswer(int id, Map
{
var answer = answers[id];
return (answer, $"Got answer {id}");
}
OK! That's more like it, we don't have a dependency on an external log, we're not manually concatenating to a log, and we have very precise performance characteristics.
But let's have add some other functions:
```c#
(string text, string log) GetQuestion(int id, Map<int, string> questions)
{
return (questions[id], $"Got question {id}");
}
(string text, string log) AskPerson(int id, Map<int, string> person, string question)
{
return ($"Hello {person[id]}, {question}", $"Asked person {id}");
}
The problem is now we have lots of separate log values a poor composition story:
```c#
var (question, log1) = GetQuestion(1, questions);
var (answer, log2) = GetAnswer(1, answers);
A composed solution would look like this:
```c#
var (text, logN) = AskPerson(42, people, GetQuestion(1, questions));
But clearly we're not there, as the return type from GetQuestion gets in the way and we still have no way to join the log together that doesn't involve the code doing it manually.
Enter function composition:
```c#
static Func(Func
a =>
{
var (valueA, logA) = f(a);
var (valueB, logB) = g(valueA);
return (valueB, logA + logB);
};
This may look a bit crazy at first, but it takes two arguments, both functions. One from `A -> (B, string)` the other to `B -> (C, string)`.
Notice how it concatenates the `logA` and `logB` for us?
If we slightly rewrite our original functions, we can inject the global state also:
```c#
static Func<int, (string text, string log)> GetQuestion(Map<int, string> questions) =>
id =>
(questions[id], $"Got question {id}");
static Func<int, Func<string, (string text, string log)>> AskPerson(Map<int, string> people) =>
id =>
text => ($"Hello {people[id]}, {text}", $"Asked person {id}");
Now if I call:
```c#
var getQuestion = GetQuestion(questionsDb);
var askPerson = AskPerson(peopleDb);
The `getQuestion` is function that takes an `int` and returns a `(string, string)`. It's captured the `questionsDb`. And `askPerson` is a function that takes an `int` and returns a `string -> (string, string)`.
That means we can all `askPerson` with a person ID to get a `string -> (string, string)`. If we `Compose` those functions we get:
```c#
var questioner = Compose(getQuestion, askPerson(1));
This gives us a Func that calls GetQuestion and AskPerson and also logs everything without us having to know how the log works.
This is a full example of it:
```c#
Map
(1, "What's the meaning of life?"),
(2, "Is there anybody out there?"));
Map
(1, "Mr Yossu"),
(2, "Louthy"));
var getQuestion = GetQuestion(questionsDb);
var askPerson = AskPerson(peopleDb);
var questioner = Compose(getQuestion, askPerson(1));
// ("Hello Mr Yossu, What's the meaning of life?", "Got question 1nAsked person 1")
var result1 = questioner(1);
// ("Hello Mr Yossu, Is there anybody out there?", "Got question 2nAsked person 1")
var result2 = questioner(2);
Another aspect to this is that the string concatenation is actually _monodial_.
What does monoidal mean? A monoid has two properties:
* It has a _unit_ value. Often called `Empty`
* It must have an associative binary operator. It is usually called `Append`.
Now, language-ext supports monoids (although C# fights us). And it already has one for `string` called `TString`, other monoids are `MSeq`, `MLst`, `MSet`, and so we can do some more cunning stuff. Let's update our `Compose` function:
```c#
static Func<A, (C, W Log)> Compose<MonoidW, W, A, B, C>(Func<A, (B Value, W Log)> f, Func<B, (C Value, W Log)> g)
where MonoidW : struct, Monoid<W> =>
a =>
{
var (valueA, logA) = f(a);
var (valueB, logB) = g(valueA);
return (valueB, default(MonoidW).Append(logA, logB));
};
}
Notice how the resulting Log is now of type W, and there's another generic argument called MonoidW, which is constrained to be struct, Monoid<W>.
The String.Join has also gone, and is now replaced with:
```c#
default(MonoidW).Append(logA, logB);
So, now we can inject any logging mechanism we like into the composed behaviour:
```c#
var questioner = Compose<MSeq<string>, Seq<string>, int, string, string>(
getQuestion,
askPerson(1)
);
This will log using Seq<string>.
Now, all this may seem like a lot of work to create some logging abstractions. But what we've actually created here is __a monad__.
The Writer monad to be exact. Which already exists in language-ext. And what you use the Writer monad you get all this stuff for free.
The general idea of performing 'hidden' operations (like the log append) _between_ functions is what monads are all about. So, if you're looking to learn how to do the functional thing well, then it's worth understanding everything above.
So, with the Writer monad you get this:
```c#
static Writer
from q in Return(questions[id])
from _ in Log($"Got question {id}")
select q;
static Writer
from p in Return(people[id])
from _ in Log($"Gog question {id}")
select $"Hello {p}, {text}";
I used a couple of helper functions:
```c#
static Writer<MSeq<string>, Seq<string>, A> ReturnW<A>(A value) =>
Writer<MSeq<string>, Seq<string>, A>(value);
static Writer<MSeq<string>, Seq<string>, Unit> LogW(string message) =>
tell<MSeq<string>, Seq<string>>(Seq1(message));
Writer is the constructor and tell is the function that logs to the monoid.
And now the composed version looks like this:
```c#
var question = from q in GetQuestion(1, questionsDb)
from t in AskPerson(2, peopleDb, q)
select t;
Personally I love the way that LINQ (which is the C# support for monads) can make everything so declarative.
But now we have a new problem. And that is that `questionsDb` and `peopleDb` are directly involved again. And that is because there is no state being inputted to the composed functions.
If you remember our original functions how they returned a value and a log (some state), but they dropped the incoming state other that the arguments. Well the `State` monad deals with that for us...
First, let's create a type that encapsulates the _world_ as it is before we call any of our code. Imagine taking a snapshot of the state of your program. In reality you only need to capture the things that your code will need, but it's a useful idea to keep in mind:
```c#
class World
{
public readonly Map<int, string> Questions;
public readonly Map<int, string> People;
public readonly Seq<string> Output;
public World(Map<int, string> questions, Map<int, string> people, Seq<string> output)
{
Questions = questions;
People = people;
Output = output;
}
public string GetQuestion(int id) =>
Questions[id];
public string GetPerson(int id) =>
People[id];
public World Log(string text) =>
new World(Questions, People, Output.Add(text));
}
So, this represents all the _stuff_ that is usauall being called directly by things like Log.Error or ticket.FseTravelType = GetAnswer(PrimaryTravelTypeQuestionID)
We can now create some reusable functions that work with our World type:
```c#
static State
from s in get
from _ in put(s.Log(log))
select unit;
static State
from s in get
from _ in Log($"Got question {id}")
select s.GetQuestion(id);
static State
from s in get
let p = s.GetPerson(id)
from _ in Log($"Got question {id}")
select $"Hello {p}, {text}";
So, now when I use those functions:
```c#
var question = from q in GetQuestion(2)
from t in AskPerson(1, q)
select t;
You'll notice I am not providing questionsDb or peopleDb. It is done like so:
```c#
var world = new World(questionsDb, peopleDb, Seq
var (text, world2, _) = question(world);
```
Obviously the more you build out of these monads, the less you have to run these invocations to get the results. You compose bigger and bigger chunks into higher levels of abstraction. With all operations being pure.
Composition is the essence of functional programming in my humble opinion. To successfully compose smaller functions into bigger ones, we must be able to trust what's inside those smaller functions. Otherwise you have non-deterministic and frankly unknowable code. We can get away with this when our applications are small, but not when they grow to multi-million line behemoths.
All of the examples in this comment are here
@louthy I just want to say how impressed and grateful I am that you put so much time and effort into answering questions and providing insight into a lot of this stuff. I follow everything with interest even though I'm only lurking for the most part.
Cheers
@louthy I can't believe you put so much effort into typing that answer! That's amazing. I'm going to print it out and read it more carefully.
As it happens, I've been reading Functional Programming in C# by Enrico Buonanno (on my third time through), and he explains a lot of the same things you're saying, which is good. I'm reading the book over and over again, trying to get it all clear. Your library helps a lot, as you have production-ready code which I can use in my experiments.
As for your specific comments, as it happens, GetQuestion is a pure function, I just simplified it for this post. The real function takes all the data it needs in its arguments (five of them) and is pure in every sense of the word. It still isn't functional, in the sense that it doesn't use Try or Option (not sure which is better yet), but I'm working on that.
The other problem is that I'm working on a very large code base (over ten years old with a lot of devs on it), that was developed in a very strict OO/imperative style, so I can't switch to FP. I'm trying to use any FP concepts that are beneficial, but this sin't going to be real FP code, much as I would like it to be! I had a hard time persuading the CTO to let me introduce a mild variation of Either, even after I demonstrated clearly how it had very little impact on the code base, but provided huge benefits over the way we currently handle exceptions. I have to tread a careful path here!
Thanks again for all the help. I'm sure I'll have lots more questions, but if I keep getting answers like this, I can see me going a long way! Maybe I'll collect all your answers and publish them as a book in your name :)
@louthy I just want to say how impressed and grateful I am that you put so much time and effort into answering questions and providing insight into a lot of this stuff.
I think that's the understatement of the year!
As it happens, I've been reading Functional Programming in C# by Enrico Buonanno (on my third time through), and he explains a lot of the same things you're saying, which is good. I'm reading the book over and over again, trying to get it all clear.
Wow, this looks like a great book. I just bought it. I am very excited to read it.
Thanks for mentioning it! :)
As it happens, I've been reading Functional Programming in C# by Enrico Buonanno (on my third time through), and he explains a lot of the same things you're saying, which is good. I'm reading the book over and over again, trying to get it all clear.
Wow, this looks like a great book. I just bought it. I am very excited to read it.
Thanks for mentioning it! :)
It's really excellent. I've read quite a few FP books, and watched endless hours of video, and nothing comes close to being as clear as this book. He even manages to explain monads in a way that makes you realise that there's nothing complex about them at all. Every other "explanation" I've seen or read makes such a dog's breakfast of the whole thing that you wonder why anyone would bother!
I'm on my third way through it, and will probably read it again and again. I strongly recommend doing the exercises (something I'm normally too lazy to do), as it made a big difference.
Functional Programming in C# by Enrico Buonanno
I鈥檓 one of the technical proofers for that book, so I鈥檇 recommend it highly :) Enrico did a great job.
Functional Programming in C# by Enrico Buonanno
I鈥檓 am a technical proofer for that book, so I鈥檇 recommend it highly :) Enrico did a great job.
Yeah, I saw he mentioned you in the beginning. His library is obviously closely modelled on yours, which is one reason why it's good, because I can use his book, but experiment with a library I would use in production.
The one thing I'd really like to see is a full end-to-end solution. Although his examples were better than most, they were still generally small workflows. I'd like to see how you'd apply FP to a full solution, from front end right down to the database. Be useful to see at what points you need to drop out of FP (if any), and where everything sits, compared to the imperative style, where you'd likely have a client, service, business logic layer and repository.
I know this would be a lot of work, but it would be hugely useful to people like me.
Hint hint :)
@louthy Just been looking at the sample code you added, and I get a compiler error on line 110...
Seq<string>' does not contain a definition for 'Add' and no accessible extension method 'Add' accepting a first argument of type 'Seq<string>' could be found (are you missing a using directive or an assembly reference?)
Any ideas? Thanks again.
@MrYossu Update to the latest beta release (you'll need to tick the _ show pre-release_s check in nuget (your Mass changes are in there too).
@louthy Thanks for that, I can see it now :)
@louthy Paul, I read (and re-read) your post last night, and most of it makes sense to me, it's basically what EB says in his book. I got a bit lost with the monoids, but I probably just need to re-read it some more.
However, it occurred to me that it was largely based on my simplified (and perhaps misleading) sample code. If you don't mind, if I show you what my code is really doing, then maybe you can show me how to improve it.
The situation is like this. I have to download information from a 3rd party API. The information comes in the form of an object graph, containing questions and answers, where each question may have multiple answers. In theory, all the expected questions should be in the object graph, and each question should have all the expected answers, and all data should be in the correct format. However, as I'm working with a 3rd party API, and not a great one at that, I need to code defensively.
Once I have the data, I need to update a ticket object (POCO, pulled from a database) with the information.
The imperative version of the code (without much in the way of error checking on the incoming data) looked like this..
```c#
ticket.FseTravelTime = GetDecimalAnswer(TravelTimeQuestionID, TravelTime_TotalTravelTime_AnswerID, codes, responses);
ticket.FseArrivalTime = GetDateTimeAnswer(ArrivalDepartureTimeQuestionID, ArrivalDepartureTime_ArrivalTime_AnswerID, codes, responses);
...where the methods use the question ID, answer ID, a set of codes (pulled from our database) and the responses from the 3rd party API. The helper methods you see there look like this...
```c#
public static DateTime GetDateTimeAnswer(string questionID, string answerID, List<FormDotComCode> codes, WSQuestionResponseForm[] responses) =>
DateTime.ParseExact(GetTextAnswer(questionID, answerID, codes, responses), "M/dd/yyyy h:mm tt", CultureInfo.InvariantCulture);
These all depend on the GetTextAnswer() method, which looks up the answer in the object graph, and returns the data if it finds it, or specific message if not. This needs improving by converting it to return an Option<string>, but I know how to do that.
My main requirement at this point is to go through all the questions (of which there are quite a few) pulling in the data and updating the ticket object as I go along, but collecting any errors as I go along, so I can report that the end. In general, errors getting the answers will not be a fatal error.
My first attempt at this, which is where this issue started was to try something like this...
c#
Try(() => GetDecimalAnswer(TravelTimeQuestionID, TravelTime_TotalTravelTime_AnswerID, codes, responses))
.Match(s => ticket.FseTravelTime = s.IfNone(0), e => errors += $"Travel time: {e.Message}. ");
As you can see, this version uses Option for the data, and substitutes an appropriate default value is the data were missing. In the case of an error, it appends the exception message to the errors string variable.
So, to go back to your points, as far as I can see, my helper methods (GetDecimalAnswer() etc) are all pure, as they are only dependent on their parameters. I don't think I have any side-effects (other than modifying the ticket object, but I don't see how I can avoid that), other than the fact that I'm modifying the errors variable. I guess I could pass that in as a parameter, which would avoid that side-effect.
Don't know if my waffle has confused you! If this makes any sense, please could you tell me if I'm using a reasonable FP approach here, and if not, how the code can be improved.
Thanks again for all your help.
@louthy
Two things...
1) Any chance you could read the waffle I posted on 24 Oct 2018 and comment?
2)I've just been re-reading your excellent reply from 23 Oct 2018, and am slowly getting my head around the concepts. (rest snipped) Some more playing with the code sorted out my other question :)
Thanks again to @louthy and @bender2k14 for all the help. I definitely have a much better idea of how this stuff works now. Still a long way, and many more questions to go though!
Most helpful comment
One thing I'd suggest is trying to stick to a few golden rules:
Composition is the name of the game. By writing composable functions that have no side-effects you make it easier to create higher level abstractions. The example you gave above may be using types from this function library, but you're doing it in a very imperative way.
So, this:
```c#
Try(() => GetAnswer(PrimaryTravelTypeQuestionID))
.Match(
s => ticket.FseTravelType = s,
e => Log.Error(e));
When we write it like this, it becomes really clear what the external dependencies are. So, how do we improve the situation? Well, we want the function to only depend on it's arguments, not some global state:
```c# answers, string log)
(string answer, string log) GetAnswer(int id, Map
{
var answer = answers[id];
log = log + $"Got answer {id}";
return (answer, log);
}
Why should this function know anything about _how_ to log? the
+operation is the mechanics of logging, we shouldn't know about it. And, wow many functions are we going to have to write this in? What happens when we change our minds? Could this function die if a log file gets full? Are there any performance impacts to this?All of these things makes the manually written log code bad.
So, let's change it slightly: answers)
```c#
(string answer, string log) GetAnswer(int id, Map
{
var answer = answers[id];
return (answer, $"Got answer {id}");
}
The problem is now we have lots of separate log values a poor composition story:
```c#
var (question, log1) = GetQuestion(1, questions);
var (answer, log2) = GetAnswer(1, answers);
But clearly we're not there, as the return type from
GetQuestiongets in the way and we still have no way to join the log together that doesn't involve the code doing it manually.Enter function composition:
```c#
static Func(Func a =>
{
var (valueA, logA) = f(a);
var (valueB, logB) = g(valueA);
Now if I call:
```c#
var getQuestion = GetQuestion(questionsDb);
var askPerson = AskPerson(peopleDb);
This gives us a
Functhat callsGetQuestionandAskPersonand also logs everything without us having to know how the log works.This is a full example of it:
```c# questionsDb = Map(
Map
(1, "What's the meaning of life?"),
(2, "Is there anybody out there?"));
Map peopleDb = Map(
(1, "Mr Yossu"),
(2, "Louthy"));
var getQuestion = GetQuestion(questionsDb);
var askPerson = AskPerson(peopleDb);
var questioner = Compose(getQuestion, askPerson(1));
// ("Hello Mr Yossu, What's the meaning of life?", "Got question 1nAsked person 1")
var result1 = questioner(1);
// ("Hello Mr Yossu, Is there anybody out there?", "Got question 2nAsked person 1")
var result2 = questioner(2);
Notice how the resulting
Logis now of typeW, and there's another generic argument calledMonoidW, which is constrained to bestruct, Monoid<W>.The
String.Joinhas also gone, and is now replaced with:```c#
default(MonoidW).Append(logA, logB);
This will log using
Seq<string>.Now, all this may seem like a lot of work to create some logging abstractions. But what we've actually created here is __a monad__.
The
Writermonad to be exact. Which already exists in language-ext. And what you use theWritermonad you get all this stuff for free.The general idea of performing 'hidden' operations (like the log append) _between_ functions is what monads are all about. So, if you're looking to learn how to do the functional thing well, then it's worth understanding everything above.
So, with the
Writermonad you get this:```c#, Seq, string> GetQuestion(int id, Map questions) =>
static Writer
from q in Return(questions[id])
from _ in Log($"Got question {id}")
select q;
static Writer, Seq, string> AskPerson(int id, Map people, string text) =>
from p in Return(people[id])
from _ in Log($"Gog question {id}")
select $"Hello {p}, {text}";
Writeris the constructor andtellis the function that logs to the monoid.And now the composed version looks like this:
```c#
var question = from q in GetQuestion(1, questionsDb)
from t in AskPerson(2, peopleDb, q)
select t;
So, this represents all the _stuff_ that is usauall being called directly by things like
Log.Errororticket.FseTravelType = GetAnswer(PrimaryTravelTypeQuestionID)We can now create some reusable functions that work with our
Worldtype:```c# Log(string log) =>()
static State
from s in get
from _ in put(s.Log(log))
select unit;
static State GetQuestion(int id) =>()
from s in get
from _ in Log($"Got question {id}")
select s.GetQuestion(id);
static State AskPerson(int id, string text) =>()
from s in get
let p = s.GetPerson(id)
from _ in Log($"Got question {id}")
select $"Hello {p}, {text}";
You'll notice I am not providing());
questionsDborpeopleDb. It is done like so:```c#
var world = new World(questionsDb, peopleDb, Seq
var (text, world2, _) = question(world);
```
Obviously the more you build out of these monads, the less you have to run these invocations to get the results. You compose bigger and bigger chunks into higher levels of abstraction. With all operations being pure.
Composition is the essence of functional programming in my humble opinion. To successfully compose smaller functions into bigger ones, we must be able to trust what's inside those smaller functions. Otherwise you have non-deterministic and frankly unknowable code. We can get away with this when our applications are small, but not when they grow to multi-million line behemoths.
All of the examples in this comment are here