-
Notifications
You must be signed in to change notification settings - Fork 136
Reader : functional dependency injection
The Reader class allows us to transform functions, by composing them.
E.g given two functions, one that maps from T to R and another from R to R1..
Function<T, R> fn;
Function<? super R, ? extends R1> fn2;
We can combine them to create a single function that maps from T to R1
Function<T,R1> composed = fn.andThen(fn2)
Reader allows us to map and flatMap functions in the manner of elements within a Stream, composing them together into a single function as we do so.
Normally in Java we will inject our dependencies into a class, perhaps using a framework or by a chain of factory methods, so that they are there when we execute a method that needs them. An alternative approach is, rather than execute the code that needs the dependency immediately, rather return a Function that accepts the dependency needed and have that Function execute the code.
e.g. a traditional Java service
class Service{
DAO userDao;
public User loadUser(long id){
return dao.loadUser(id);
}
}
could become
class Service{
public Function<Dao,User> loadUser(long id){
return dao->dao.loadUser(id);
}
}
Further more if we refactor this to use [Reader] instead of Function (Reader extends Function and is also a FunctionalInterface) we can manipulate the returned Function and build a chain of method calls, that for example, work with the returned User - even though we won't have the User until we supply the DAO and execute the function at the very top level.
class Service{
public Reader<Dao,User> loadUser(long id){
return dao->dao.loadUser(id);
}
}
### Further Reading
For a more detailed example of how to use the Reader monad for dependency injection see Dependency Injection using the Reader Monad in Java 8
The current code for implementing the examples looks like this
UserRepositoryImpl repo = new UserRepositoryImpl();
public Map<String,String> userInfo(String username) {
return run(new UserInfo().userInfo(username));
}
private Map<String,String> run( Reader<UserRepository, Map<String,String> > reader){
return reader.apply(repo);
}
static class UserInfo implements Users {
public Reader<UserRepository,Map<String,String>> userInfo(String username) {
For.reader(findUser(username))
.reader(user ->getUser(user.getSupervisor().getId()))
.yield(user -> boss -> "user:"+username+" boss is "+boss.getName());
return For.reader(findUser(username))
.reader(user -> getUser(user.getSupervisor().getId()))
.yield(user -> boss -> buildMap(user,boss)).unwrap();
}
private Map<String,String> buildMap(User user, User boss) {
return new HashMap<String,String> (){{
put("fullname",user.getName());
put("email",user.getEmail());
put("boss",boss.getName());
}};
}
}
static interface Users {
default Reader<UserRepository,User> getUser(Integer id){
return FluentFunctions.of( userRepository -> userRepository.get(id));
}
default Reader<UserRepository,User> findUser(String username) {
return FluentFunctions.of(userRepository -> userRepository.find(username));
}
}
static class UserRepositoryImpl implements UserRepository{
int count = 0;
User boss = new User(10,"boss","[email protected]",null);
@Override
public User get(int id) {
if(id==boss.getId())
return boss;
return new User(id,"user"+id,"user"+id+"@user.com",boss);
}
@Override
public User find(String username) {
return new User(count++,username,username+"@user.com",boss);
}
}
@Value
public class User {
int id;
String name;
String email;
User supervisor;
}
oops - my bad