How do I use Try<T> with an existing method?
See original GitHub issueHello,
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
Issue Analytics
- State:
- Created 5 years ago
- Reactions:2
- Comments:15 (14 by maintainers)
Top GitHub Comments
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:
Has:
Log.Error(e)
)Log.Error(e)
affects the worldGetAnswer(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:
GetAnswer
is a good example of something we can improve with a bit of cunning:Imagine this:
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:
So, now we’re passing in all the possible answers, and returning the chosen
answer
and the updatedlog
.But, we still have some problems (apart from lots of argument typing). And it’s this:
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:
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:
The problem is now we have lots of separate log values a poor composition story:
A composed solution would look like this:
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:
This may look a bit crazy at first, but it takes two arguments, both functions. One from
A -> (B, string)
the other toB -> (C, string)
.Notice how it concatenates the
logA
andlogB
for us?If we slightly rewrite our original functions, we can inject the global state also:
Now if I call:
The
getQuestion
is function that takes anint
and returns a(string, string)
. It’s captured thequestionsDb
. AndaskPerson
is a function that takes anint
and returns astring -> (string, string)
.That means we can all
askPerson
with a person ID to get astring -> (string, string)
. If weCompose
those functions we get:This gives us a
Func
that callsGetQuestion
andAskPerson
and also logs everything without us having to know how the log works.This is a full example of it:
Another aspect to this is that the string concatenation is actually monodial.
What does monoidal mean? A monoid has two properties:
Empty
Append
.Now, language-ext supports monoids (although C# fights us). And it already has one for
string
calledTString
, other monoids areMSeq
,MLst
,MSet
, and so we can do some more cunning stuff. Let’s update ourCompose
function:Notice how the resulting
Log
is now of typeW
, and there’s another generic argument calledMonoidW
, which is constrained to bestruct, Monoid<W>
.The
String.Join
has also gone, and is now replaced with:So, now we can inject any logging mechanism we like into the composed behaviour:
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 theWriter
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:I used a couple of helper functions:
Writer
is the constructor andtell
is the function that logs to the monoid.And now the composed version looks like this:
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
andpeopleDb
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:
So, this represents all the stuff that is usauall being called directly by things like
Log.Error
orticket.FseTravelType = GetAnswer(PrimaryTravelTypeQuestionID)
We can now create some reusable functions that work with our
World
type:So, now when I use those functions:
You’ll notice I am not providing
questionsDb
orpeopleDb
. It is done like so: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