3

I wrote a Scala function:

  def liftOrIdentity[T](f: (T, T) => T) = (a: Option[T], b: Option[T]) =>
    (a, b) match {
      case (Some(a), None) => Some(a)
      case (None, Some(b)) => Some(b)
      case (Some(a), Some(b)) => Some(f(a, b))
      case (None, None) => None
    }

Is there a name for this pattern? It is not quite an applicative functor due to cases 1 and 2. Feel free to answer with Haskell or Scala code.

Daniel Widdis
  • 8,424
  • 13
  • 41
  • 63
Felix
  • 8,385
  • 10
  • 40
  • 59
  • On collection it is `flatten` + `reduce`: `List(a, b).flatten.reduceOption(f)`. – senia Jun 19 '13 at 09:53
  • That is pretty sexy! Compared to the wall of ugly above :) Consider posting this as an answer ;) Didn't know about reduceOption, that's pretty slick! – Felix Jun 19 '13 at 10:00

4 Answers4

9

On collection it is flatten + reduce:

List(a, b).flatten.reduceOption(f)
a ++ b reduceOption f // same result
senia
  • 37,745
  • 4
  • 88
  • 129
  • I'll leave it hanging to see if there are more suggestions, but will probably accept this :) – Felix Jun 19 '13 at 10:43
  • I chose this answer because it concisely solves the problem. The use of flatten here is very nice, and I didn't know about the "oddly specific" reduceOption, but it works nicely for my problem. – Felix Jun 20 '13 at 07:27
  • @Felix: Note that you are actually don't need `flatten` here: `a ++ b reduceOption f`. – senia Jun 20 '13 at 07:48
  • Can you maybe tell me where Option gets its ++ method from? http://www.scala-lang.org/api/current/index.html#scala.Option I see it listed, it works in REPL, but it doesnt extend TraversableLike and it doesn't implement it :S I'm confused! – Felix Jun 20 '13 at 08:10
  • 1
    `Option` has not `++` method. In documentation it has green color and `Implicit information` in description. As you can see from the `Implicit information` there is an implicit conversion [`Option.option2Iterable`](http://www.scala-lang.org/api/current/index.html#scala.Option$). – senia Jun 20 '13 at 08:17
  • Thanks, I don't know why I didn't read that :) I guess the API sometimes is a bit of an information overload. On another note, if you don't unfold to "Full signature", the signature is just plain wrong right? "def ++[B](that: GenTraversableOnce[B]): Option[B]" doesn't return Option[B]... – Felix Jun 20 '13 at 11:04
7

I'm reminded of the Alternative type class in Haskell's Control.Applicative:

class Applicative f => Alternative f where
    empty :: f a
    (<|>) :: f a -> f a -> f a

A general version of your function for any instance of Alternative might look like this:

liftOrAlternative :: (Alternative f) => (a -> a -> a) -> f a -> f a -> f a
liftOrAlternative f a b = f <$> a <*> b <|> a <|> b

ghci> liftOrAlternative (+) (Just 1) Nothing
Just 1
ghci> liftOrAlternative (+) (Just 1) (Just 2)
Just 3
ghci> liftOrAlternative (+) Nothing Nothing
Nothing

For Scala, I think the closest analogy to Alternative would be the ApplicativePlus type class from Scalaz.

 def liftOrAlternative[A, F[_]: ApplicativePlus](f: (A, A) => A)(a: F[A], b: F[A]): F[A] =
   f.lift[F].apply(a, b) <+> a <+> b

I admit that liftOrAlternative is not a great name. After reading Twan van Laarhoven's answer, I think his suggestion of unionWith is much better at expressing what the function actually does.

Ben James
  • 121,135
  • 26
  • 193
  • 155
4

This function is similar to the Haskell containers function

Data.Map.unionWith :: (a -> a -> a) -> Map k a -> Map k a -> Map k a

I think unionWith is a good name for it in general. The more usual applicative operator would be an intersectionWith (aka. zipWith).

Data.Map.intersectionWith :: (a -> b -> c) -> Map k a -> Map k b -> Map k c
Twan van Laarhoven
  • 2,542
  • 15
  • 16
-1

In Haskell, there is something similar called liftM2.

liftM2 :: Monad m => (a1 -> a2 -> r) -> m a1 -> m a2 -> m r
Tobias Brandt
  • 3,393
  • 18
  • 28