Tuples vs functions vs generics

Hy there!

I recently started to use tapir. The server logic in there is an N tuple => Future[Either[E, A]] function, where the N tuple part is based on a builder (both the size of the tuple and the element types in the tuple).

I want to achieve an akka-http directive like server logic;

def authFunc(b: Boolean): Future[Either[String, Int]] = 
  if(b) Future.successful(1.asRight[String]) else Future.successful("error".asLeft[Int])
def appLogic(id: Int, name: String, s1: String, s2: String): Future[Either[String, Int]] = 
Future.successful(1.asRight[String])

val endpointLogic: ((Boolean, String)) => Future[Either[String, Int]] = {
  withF1(authFunc){ userId =>
    unwrap0 { name =>
      appLogic(userId, name, "", "")
    }
  }
}

val endpointLogic2: ((Boolean, String, String)) => Future[Either[String, Int]] = {
  withFN(authFunc){ userId =>
    unwrap1 { name: String =>
      unwrap0 { email: String =>
        appLogic(userId, name, email, "")
      }
    }
  }
}

val endpointLogic3: ((Boolean, String, String, String)) => Future[Either[String, Int]] = {
  withFN(authFunc){ userId: Int =>
    unwrapN { (dummy: String) =>
      unwrap1 { name: String =>
        unwrap0 { email: String =>
          appLogic(userId, name, email, dummy)
        }
      }
    }
  }
}

For this, I need something that can split a (A, B, C) => input into an A => (B, C) => input.

Both the unwraps and the withFs are easy to implement with shapeless:

def unwrap0[I, E, O](uf: I => Future[Either[E, O]]): I => Future[Either[E, O]] = uf
def unwrap1[I, K, E, O](uf: I => K => Future[Either[E, O]]): ((I, K)) => Future[Either[E, O]] = i => uf(i._1)(i._2)
def unwrapN[IN_TUPLE, OUT_TUPLE, I, E, O](uf: I => OUT_TUPLE => Future[Either[E, O]])
  (implicit ic: IsComposite.Aux[IN_TUPLE, I, OUT_TUPLE])
: IN_TUPLE => Future[Either[E, O]] =
{inTuple =>
  uf(ic.head(inTuple))(ic.tail(inTuple))
}

def withF1[I, K, E, A, O](af: I => Future[Either[E, A]])
  (uf: A => K => Future[Either[E, O]])
  (implicit ec: ExecutionContext)
: ((I, K)) => Future[Either[E, O]] =
{ i =>
  af(i._1).flatMap {
    case Left(e) => Future.successful(Left(e))
    case Right(u) =>
      uf(u)(i._2)
  }
}

def withFN[IN_TUPLE, OUT_TUPLE, I, E, A, O](af: I => Future[Either[E, A]])
  (uf: A => OUT_TUPLE => Future[Either[E, O]])
  (implicit ic: IsComposite.Aux[IN_TUPLE, I, OUT_TUPLE], ec: ExecutionContext)
: IN_TUPLE => Future[Either[E, O]] = 
{ inTuple =>
  val t = ic.head(inTuple)
  af(t).flatMap {
    case Left(e) => Future.successful(Left(e))
    case Right(u) =>
      val outTuple = ic.tail(inTuple)
      uf(u)(outTuple)
  }
}

The problems:

  • 0,1,N versions:
    • the N version not working with non tuples
    • the N version returns Tuple1[A] instead of A for the input (B, A) :slight_smile:
  • explicit types:
    • the third example not working bcs the compiler can’t figure out the nested N calls
    • bcs of the upper and lower function definitions it would be nice if all of the : String typedefs could be removed

I would be happy for any idea or advice in this matter, bcs my server logic functions are really ugly with the build in helper(s). (I know that I can use a case (a, b, c, d) => for{}yield() but I think the hadoken style is more readable in this case, and I’m a bit mad that I can’t solve this :smiley: )

Full gist: https://gist.github.com/tg44/c0e20c64714c60df2661eb1fcd73ab83