简体   繁体   中英

Composing Options in an idiomatic way

I'm going to write this in Scala, but it's more of a functional programming question.

I have

def foo(x: A): Option[B]

and

def bar(x:B, y:B): C

What's the neatest way to do the following:

def compose(x:A, y:A): Option[C]

such that if either foo(x) of foo(y) are None, then compose(x,y) is None, otherwise compose(x,y) is bar(foo(x).get, foo(y).get). The best I could come up with is:

foo(a).flatMap( aRes => foo(b).map( bRes => bar(a,b)))

The following is syntactic sugar for your current solution:

def compose(x: A, y: A): Option[C] = for {
  fx <- foo(x)
  fy <- foo(y)
} yield bar(fx, fy)

Sometimes this approach is nicer than writing out flatMap and map , and sometimes it's not. You'll probably find that you pretty quickly develop strong preferences about this kind of thing. Either could be considered idiomatic Scala.

Since you've indicated that you're interested in the question more generally from the perspective of functional programming, however, it's worth noting that the solutions above are overkill in a sense. They take advantage of the fact that Option is monadic, but for this operation you don't actually need all of that power—the fact that Option has an applicative functor instance is enough. To summarize very informally, flatMap gives you sequencing that you don't need here, since the computation of fy doesn't depend on the computation of fx . Using the applicative functor for Option allows you to more clearly capture the fact that there's no dependency between the two computations.

The Scala standard library doesn't provide any kind of representation of applicative functors, but Scalaz does, and with Scalaz you could write your method like this (see the "appendix" of my answer here for some discussion of the syntax):

import scalaz._, Scalaz._

def compose(x: A, y: A): Option[C] = (foo(x) |@| foo(y))(bar)

This will produce the same result as the implementation above, but using a more appropriate abstraction.

How about:

for (x <- foo(a); y <- foo(b)) yield bar(x,y) 

for instance:

type A = Int
type C = (A,A)
def foo(x: A): Option[A] = if (x > 0) Some(x) else None
def bar(x: A, y: A): C = x -> y 
for (x <- foo(1); y <- foo(2)) yield bar(x,y)
// Option[C] = Some((1,2))
for (x <- foo(-1); y <- foo(2)) yield bar(x,y)
// Option[C] = None

Depending on your taste the very first could be written as:

for { 
    x <- foo(a)
    y <- foo(b) 
} yield bar(x,y) 

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM