Skip to content

Conversation

@aaronlevin
Copy link

Without thinking about the for comprehension, look at how using the map method seems to push the need to instantiate configuration all the way up to main.

The problem is that then you have to deal with "ConfigReader"'s everywhere. 

Without thinking about the for comprehension, look at how using the map method seems to push the need to instantiate configuration all the way up to main.

The problem is that then you have to deal with "ConfigReader"'s everywhere. 
@overthink
Copy link
Owner

Ok, I like where this is going, but it's still not very compelling (imo)
and your last comment spells out why: you have ConfigReaders everywhere.
In particular, we had to change existing functions to know about
ConfigReader. I'm wondering if this is where "lifting" functions "into"
the monad is helpful.

I'm imagining there are two parties involved in the test app. Party1 is
providing a library that uses simple non-monadic types (i.e. getHeader
and getAllTheData in your example). Party2 is the "client" app that has
access to some config and wants to use Party1's library. Party2 decides to
use the ConfigReader monad for accessing config, but now there's a problem:
all the functions from Party1 involve plain types and Party2 has
ConfigReaders. Assume the code in Party1 cannot be changed... What
distinct advantages do the monad approach give here? (this is all
rhetorical rambling, btw!)

@aaronlevin
Copy link
Author

We only had ConfigReaders roaming around when we started to involve functions that required Configuration. Note that the two core functions, which only depended on a UrlServiceClient, were unchanged. We haven't gained anything when contrasted with having a global Configuration object, though.

I'll try to come up with an example with your last example.

@overthink
Copy link
Owner

Maybe I misunderstood your example. It looks like after config was involved getAllTheData turned into def getAllTheData2(url: String): ConfigReader[String] (i.e. returns a ConfigReader now).

@aaronlevin
Copy link
Author

The two functions that took a UrlServiceClient (getBody and getHeader) were unchanged. Only when we needed to actually instantiate at UrlServiceClient did things start to get wrapped in a ConfigReader.

What's interesting, imo, is that the need to actually pass the Config was pushed all the way up to the outermost method. Yes, it meant that ConfigReader and maps had to be used everywhere, but that just explicitly letting you now that you're passing around an object that depends on configuration. I think this is what those FP dudes talk about when they say the external effects become explicit in your code and you're left with a "tight functional core, with effects happening on the outside" (I heard that quote somewhere).

The question becomes: do you want them around everywhere when it's just as easy to put lazy val's in a global object that readers from a config file.

@overthink
Copy link
Owner

I will take almost anything over lazy vals :) In poc we have an absolute
nightmare of initialization order problems due to lazy vals. I like lazy
vals for only one thing: saving an expensive calculation away so it's only
done once.

@aaronlevin
Copy link
Author

I can't actually read your comment because it's wrapped in an Iterator wrapped in a lazy val wrapped in a tornado.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants