Guest User

Untitled

a guest
Jul 15th, 2018
85
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 5.86 KB | None | 0 0
  1. Case for ReaderT (Reader Monad) -
  2.  
  3. PreRequisites:
  4. - I am assuming that the reader already agrees that dependency injection is generally a good thing.
  5. - For the sake of discussion most of the discussion will be a comparison to class level dependency dependency injection.
  6.  
  7. Summary:
  8. ReaderT allows for composable method level dependency injection. Method level dependency has a few advantages over class level dependencies (examples provided below). This is not to say class level DI has no advantages, I am just proposing that method level has more benefits in particular cases.
  9.  
  10. 1) Dependencies for a function call are explicit. As a result maintenance, reasoning, and testing can all be easier.
  11.  
  12. a) Only necessary dependencies have to be resolved in order to reuse the function. You don't have to resolve all the dependencies on the class, even the ones not needed to perform the function
  13.  
  14. b) Because the dependencies are in the method signature the intent becomes clearer to the caller. A method with no dependencies *should* be guaranteed to have no side effects. A method with at least one dependency can have only have a side effect with that dependency. Where as with class level you have you look up the class, look at is dependencies, look which ones the method is reusing to see if it has any side effects. Potentially the reader has to recursively look up dependencies till you look at all the dependencies or methods to achieve the same level or reasoning about the intent.
  15.  
  16. c) Method level DI can make methods more reusable. With class level DI one needs all of the class level dependencies resolved in order to reuse the method, even if the method does not actually need them. This can become a recursive problem if there is a deep call stack.
  17.  
  18. d) Method level decencies generally expose composability or coupling issues. With the exception of the fringes of a system, the more method dependencies you have the more likely the methods are not composable. I generally use a rule of 3, once I see 3 dependencies on a method it is time to take a closer look at it for breaking up or better composition.
  19.  
  20. e) Testing becomes easier because you only need to satisfy the dependencies that are required. This helps isolates tests and makes issues in the testing easier to track down because there are typically fewer dependencies that need to be stubbed.
  21.  
  22. 2) There are a few types of method level dependency injection (that I am aware of at least). Implicits, dependencies as part of the left most arguments, or dependenices as part of the return type (ReaderT style).
  23.  
  24. a) Implicits have the problem were you are no longer being explicit. You can end up down rabbit holes because an import accidentally overrode the intended dependency due to implicit scoping.
  25.  
  26. b) Parameters on the left side have all the advantages above but do not provide any additional composition capability.
  27.  
  28. c) Parameters as part of the return, tend to compose with other functions and make the code a bit cleaner. In ReaderT's case you have map/flatMap and can map/flatMap over functor types.
  29.  
  30. Mixed/Cons of ReaderT
  31. a) Method level DI is slightly more syntax than class level. However this is generally true in cases of being explicit.
  32.  
  33. b) In a code base that is primarily class level DI, there can be extra syntax to call '.run' in various places for compatibility reasons, if there is a movement towards ReaderT. However if the code base trends more to method level syntax then the extra calls to `.run` are not needed or needed less due to composition.
  34.  
  35. c) ReaderT is an implementation of the reader monad. The reader monad concept is more prevalent in functional program languages and code bases. The primary talent pool to hire from are generally OO based programers (Java or Scala as a better Java), hence this concept will be a bit foreign to most new hirers.
  36.  
  37. --- Trivial example ---
  38.  
  39. import cats.data.ReaderT
  40.  
  41. import scala.collection.mutable
  42.  
  43. trait SideEffect {
  44. def get (key:Long):Option[Long]
  45. def upsert (key:Long, value:Long):Unit
  46. def remove (key:Long):Unit
  47. }
  48.  
  49. object InMemoryKVStore extends SideEffect {
  50. val table:mutable.HashMap[Long,Long] = mutable.HashMap.empty[Long,Long]
  51.  
  52. override def get(key: Long): Option[Long] = table.get(key)
  53.  
  54. override def upsert(key: Long, value:Long): Unit = table.put(key, value)
  55.  
  56. override def remove(key: Long): Unit = table.remove(key)
  57. }
  58.  
  59. object UtilityService {
  60. def computeMultiple(multiple:Long)(value:Long): Option[Long] = {
  61. val result = multiple.toDouble * value.toDouble
  62. val resultLong = result.toInt
  63. val delta = result - resultLong.toDouble
  64. if (delta == 0) {
  65. Some(resultLong)
  66. } else {
  67. //overflowed
  68. None
  69. }
  70. }
  71.  
  72. def computeValue(key:Long)(value:Long): ReaderT[Option, SideEffect, Long] = ReaderT { sideEffect =>
  73. sideEffect.get(key).map(_ * value)
  74. }
  75. }
  76.  
  77. /*
  78. What can be seen by looking at the signatures:
  79.  
  80. 1) getMyValue is reuasable without needing to resolve any dependencies and *should* have no side effects.
  81. 2) getItsValue requires the dependency SideEffect and may have a side effect.
  82. */
  83. object MethodDI {
  84. def getMyValue (value:Long): Option[Long] = {
  85. UtilityService.computeMultiple(value/2L)(value)
  86. }
  87. def getItsValue (value:Long): ReaderT[Option, SideEffect, Long] = {
  88. UtilityService.computeValue(value/2)(value)
  89. }
  90. }
  91.  
  92. /*
  93. What can be seen by looking at the signatures:
  94. 1) All methods on the class require an instance of SideEffect to be resolved in order to be used, the more dependencies the bigger this gets.
  95. 2) Nothing can be seen by looking at the signatures, you have too look at the class declaration and do a find to see what methods may be impacted.
  96. */
  97. class ClassDI(inMemKeyStore:SideEffect) {
  98. def getMyValue (value:Long): Option[Long] = {
  99. UtilityService.computeMultiple(value/2L)(value)
  100. }
  101. def getItsValue (value:Long): Option[Long] = {
  102. UtilityService.computeValue(value/2)(value).run(inMemKeyStore)
  103. }
  104. }
Add Comment
Please, Sign In to add comment