8

I have two functions.

  def process(date: DateTime, invoice: Invoice, user: User, reference: Reference) : (Action, Iterable[Billable])

  def applyDiscount(billable: Billable) : Billable

How can I compose these so that I have a single function of (DateTime, Invoice, User, Reference) => (Action, Iterable[Billable])

Here is the poor mans way of what I want

  def buildFromInvoice(user: User, order: Invoice, placementDate: DateTime, reference: Reference) = {
    val ab = billableBuilder.fromInvoice(user, order, placementDate, reference)
    (ab._1, ab._2.map(applyDiscount(_))
  }
Travis Stevens
  • 2,198
  • 2
  • 17
  • 25

1 Answers1

9

What you have (simplifying) is:

val f: A => (B, M[C]) //M is a Functor
val g: C => C

I can think of a few ways of doing this. I think my preference is:

(a: A) => g.lift[M].second apply f(a)

Or also:

(a: A) => f(a) :-> g.lift[M]  

However, there is possibly a pointfree way - although not necessarily so, of course

  • lift is a method on Function1W which lifts the function into the realm of the functor M
  • second is a method on MAB which applies the function down the right-hand-side of a Bifunctor
  • :-> is a method available to Bifunctors denoting the application of a function on the rhs.

EDIT - missingfaktor appears to be correct in saying f andThen g.lift[M].second works:

scala> import scalaz._; import Scalaz._
import scalaz._
import Scalaz._

scala> case class A(); case class B(); case class C()
defined class A
defined class B
defined class C

scala> lazy val f: A => (B, List[C]) = sys.error("")
f: A => (B, List[C]) = <lazy>

scala> lazy val g: C => C = sys.error("")
g: C => C = <lazy>

Pointfree:

scala> lazy val h = f andThen g.lift[List].second
h: A => (B, List[C]) = <lazy>
oxbow_lakes
  • 133,303
  • 56
  • 317
  • 449
  • 1
    Perhaps `f andThen g.lift[M].second`. – missingfaktor Oct 18 '11 at 19:04
  • Nicely distilled, amazing how much clearer things are when you can *only* see the types! – retronym Oct 18 '11 at 20:09
  • Awesome answer. I totally understand each step here after playing around in the repl. Applying this to my problem I came up with these two solutions: val bfc = billableBuilder.fromContract(_: User, _: Contract, _: DateTime, _: Option[Order]) :-> (applyDiscount(_)).lift[Iterable] and val bfc = (applyDiscount(_)).lift[Iterable].second apply billableBuilder.fromContract(_: User, _: Contract, _: DateTime, _: Option[Order]) – Travis Stevens Oct 18 '11 at 20:48
  • Also note that I could not get the 'andThen' working for my situation. andThen seems to be only defined on Function1 where as apply is defined on all FunctionXX traits. – Travis Stevens Oct 18 '11 at 20:52
  • @oxbow_lakes Do you have any insight on why `:->` implicitly works on the second argument (and there is no symmetric way to apply this on the first argument). Or would this be worth another question. – Debilski Oct 19 '11 at 08:55
  • @Debilski - you can use `<-:` for the first element of the tuple. Or is this not the question? – oxbow_lakes Oct 19 '11 at 09:11
  • @oxbow_lakes Yes but with `<-:` I’d have to invert the order of f and g. Which makes this operator even more confusing. I suppose there must be a reason or convention for that. – Debilski Oct 19 '11 at 09:15
  • it's so you can do `l <-: pair :-> r` for functions l and r – oxbow_lakes Oct 19 '11 at 16:11
  • Why `lazy val` instead of `def`? And, on trunk, `???` instead of `sys.error("")`... :-) – Daniel C. Sobral Nov 03 '11 at 00:27