Skip to content

Reader : functional dependency injection

johnmcclean-aol edited this page Nov 22, 2016 · 3 revisions

Reader monad

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.

Functional Dependency Injection

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;
	
         }
Clone this wiki locally