Consider this code:
def f1(a: Int, b: Int) = a + b
def f2(a: Option[Int], b: Option[Int]): Int = (a, b) match {
case (Some(x), Some(y)) => x + y
case _ => throw new IllegalArgumentException
}
println(f1(10, 20))
println(f2(Some(10), Some(20)))
I heard you can "lift" f1 to be like f2. As a beginner I have the following question:
What is lifting and why it used? In terms of implementation, how can I "lift" f1?
Any explanation is greatly appreciated as it is a bit tricky to find why I would "lift" something
Why: when you have a function with signature like f1 and want to "call it" on Option[Int]s (or List[Int]s, etc)
How: you could write it directly:
def lift2option[A, B, C](f: (A, B) => C): (Option[A], Option[B]) => Option[C] = ???
I'm leaving it undefined because you should try writing it yourself; your definition of f2 should be a good starting point. Note that I made it return Option[Int] instead of Int. Later I can edit and give the answer if you want.
And then instead of defining f2 as separate function, you do:
val f2 = lift2option(f1 _)
println(f2(Some(10), Some(20)))
Of course, the point is that now for any function with a signature like f1 you can get an f2 equivalent.
It can be further generalized to work not only for Option, but you'll want to look into that later.
I just wanted to add that lifting a function can be used as an alternative to mapping over a functor. For example, if you had 2 Option[Int] objects which you wanted to apply f1 to, you could do this:
val sum: Option[Int] = option1.flatMap { x => option2.map{ y => x + y } }
Note that the result is an Option[Int]. As Alexey Romanov said, the return type of f2 should also be an Option. The whole point of Option is to let you do operations on a value without fear of NullPointerExceptions or other errors because the value doesn't exist.
However, this mapping is a bit verbose, and it's annoying having to decide when you need to use flatMap and map. This is where lifting comes in handy.
Let's define f2 a little better to handle Nones:
def f2(a: Option[Int], b: Option[Int]): Option[Int] =
a match {
case Some(x) => b match {
case Some(y) => Some(x + y)
case None => None
}
case None => None
}
We can also define this in terms of f1 by replacing x + y with f1(x + y)
def f2(a: Option[Int], b: Option[Int]): Option[Int] =
a match {
case Some(x) => b match {
case Some(y) => Some(f1(x, y))
case None => None
}
case None => None
}
Right now, f2 doesn't need to know anything about how to add numbers, it just uses f1 to add them. We could even make f1 a parameter of f2, in fact.
def f2(f1: (Int, Int) => Int)(a: Option[Int], b: Option[Int]): Option[Int] =
a match {
case Some(x) => b match {
case Some(y) => Some(f1(x, y))
case None => None
}
case None => None
}
See what happened there? We just used f2 to "lift" f1 from (Int, Int) => Int to (Option[Int], Option[Int]) => Option[Int]. Let's rename it lift2, actually. We could also make it more generic:
def lift2[A, B, C](f1: (A, B) => C)(a: Option[A], b: Option[B]): Option[C] =
a match {
case Some(x) => b match {
case Some(y) => Some(f1(x, y))
case None => None
}
case None => None
}
lift2 is now a function that takes a function of type (A, B) => C (here, A, B, and C are all Int for f1) and returns another function of type (Option[A], Option[B]) => Option[C]. Now, we don't have to use those awkward nested maps and flatMaps. You can just do this:
val sum: Option[Int] = lift2(f1)(option1, option2)
You can also define lift3, lift4, etc., of course, but it's probably easier to define just a lift1 function and use currying to do the rest.
Of course, you can only lift a function if you know how to take apart and put together the type that you are lifting to. For example, if Some were an object with a private unapply method, and it were impossible to pattern match on it, you wouldn't be able to lift f1. The same would happen if the constructor for Some were private and it were impossible for you to make new Options.
Edit: Here's how you can add multiple Option[Int] objects together with f1 and the lift2 function.
val f2 = lift2(f1)
val optionSum = f2(f2(option1, option2), option3)
Without f2, it'd look something like this
val sum1 = option1 match {
case Some(x) => option2 match {
case Some(y) => Some(f1(x, y))
case None => None
}
case None => None
}
val finalSum = sum1 match {
case Some(x) => option3 match {
case Some(y) => Some(f1(x, y))
case None => None
}
case None => None
}
Related
TLDR: I'm looking for functional programming patterns for composing both the success and failure paths of recursive, failable functions.
Scastie link for the example code: https://scastie.scala-lang.org/apUioyJsSdaziPfiE14CoQ
Given a recursive datatype and failable method that operates on it:
sealed trait Expr
case class Lit(x: Int) extends Expr
case class Div(lhs: Expr, rhs: Expr) extends Expr
def evaluate(expr: Expr): Either[String, Int] = ???
Typical examples of functional composition show how to elegantly implement these things:
def evaluate(expr: Expr): Either[String, Int] = expr match {
case Lit(x) => Right(x)
case Div(l, r) =>
for {
x <- evaluate(l)
y <- evaluate(r)
res <- if (y != 0) Right(x / y) else Left("Divide by 0!")
} yield res
}
evaluate(Div(Lit(8), Div(Lit(3), Lit(0)))) // Left("Divide by 0!")
This is great, except that sometimes you also want to do some kind of composition of the error messages. This is especially useful if you want parent nodes to add information to errors propagated from their children.
Perhaps I want to return an error message with context about the entire expression rather than just the information that there was a divide by 0 somewhere:
def evaluate2(expr: Expr): Either[String, Int] = expr match {
case Lit(x) => Right(x)
case Div(lhs, rhs) =>
val l = evaluate2(lhs)
val r = evaluate2(rhs)
val result = for {
x <- l
y <- r
res <- if (y != 0) Right(x / y) else Left(s"$x / 0")
} yield res
result.orElse {
Left(s"(${l.merge}) / (${r.merge})") // take advantage of Any.toString
}
}
evaluate2(Div(Lit(8), Div(Lit(3), Lit(0)))) // Left("(8) / (3 / 0)")
This isn't terrible, but it isn't great either. It's 4 lines of business logic vs. 5-6 of boiler plate.
Now, I'm not the best at functional programming, and I don't know much about Cats and Scalaz, but I do now that this smells like a reusable higher-order function. From the type that I want, I can derive a pretty useful utility function:
def flatMap2OrElse[R, A, B](x: Either[R, A], y: Either[R, A])
(f: (A, A) => Either[R, B])
(g: (Either[R, A], Either[R, A]) => R): Either[R, B] =
(x, y) match {
case (Right(a), Right(b)) => f(a, b)
case _ => Left(g(x, y))
}
Then it's trivial to write a concise form:
def evaluate3(expr: Expr): Either[String, Int] = expr match {
case Lit(x) => Right(x)
case Div(lhs, rhs) =>
flatMap2OrElse(evaluate3(lhs), evaluate3(rhs)) {
(x, y) => if (y != 0) Right(x / y) else Left(s"$x / 0")
} {
(x, y) => s"(${x.merge}) / (${y.merge})"
}
}
evaluate3(Div(Lit(8), Div(Lit(3), Lit(0)))) // Left("(8) / (3 / 0)")
The orElse function taking Eithers is a bit weird, but it's my function and it can be weird if I want it to.
In any case, it seems to me that there should be a pattern here. Is the style of evaluate2 the canonical way of doing it or are there utilities/abstractions I should be looking at to better handle this kind of thing?
EDIT
New Scastie: https://scastie.scala-lang.org/p0odf16PTLOTJPYSF9CGMA
This is a partial answer, but still requires a custom function that feels like it should exist somewhere. I think with just flatMap2 we can do this pretty clearly without the boutique flatMap2OrElse:
def flatMap2[R, A, B](x: Either[R, A], y: Either[R, A])
(f: (A, A) => Either[R, B]): Either[R, B] =
(x, y) match {
case (Right(a), Right(b)) => f(a, b)
case (Left(a), _) => Left(a) // Need Either[R, B]
case (_, Left(b)) => Left(b)
}
def evaluate4(expr: Expr): Either[String, Int] = expr match {
case Lit(x) => Right(x)
case Div(lhs, rhs) =>
val l = evaluate4(lhs)
val r = evaluate4(rhs)
flatMap2(l, r)((x, y) => if (y != 0) Right(x / y) else Left(s"$x / 0"))
.orElse(Left(s"(${l.merge}) / (${r.merge})"))
}
evaluate4(Div(Lit(8), Div(Lit(3), Lit(0)))) // Left("(8) / (3 / 0)")
That being said, this concept should generalize beyond flatMap2. It just feels like this is already a thing.
Specifically:
scala> def f(n: Seq[Any]) = n match {
case Nil => "Empty"
case h :: t => "Non-empty"
}
f: (n: Seq[Any])String
scala> f(Stream())
res1: String = Empty
scala> f(List(1))
res17: String = Non-empty
scala> f(Stream(1))
scala.MatchError: Stream(1, ?) (of class scala.collection.immutable.Stream$Cons)
at .f(<console>:13)
... 33 elided
There are a lot of other ways to implement this, but at written the code was statically safe and failed at run time. What's going on?
For Stream, the concat symbol should be #::, the pattern match should like:
def f(n: Seq[Any]) = n match {
case Nil => "Empty"
case h :: t => "Non-empty"
case h #:: t => "Non-empty stream"
}
for :: is for List / Seq type(List extends from Seq:) ), see:
final case class ::[B](override val head: B, private[scala] var tl: List[B]) extends List[B] {
You can only use :: to deconstruct a List and not a Stream. Since the Stream you provide does not match a List, you get a MatchError. If you want f to support streams using that type of matching (extractors), you can use #::.
def f(n: Seq[Any]) = n match {
case Nil => "Empty"
case h :: t => "Non-empty"
case h #:: t => "Non-empty"
}
In general, this approach is very fragile because both extractor types shown above will only work for those two types of Seq. Others maybe break. If all you care about is determining whether or not the Seq is empty or not, then simply use n.nonEmpty or n.isEmpty and deal with the Boolean result. Otherwise, trying to provide an exhaustive match on a trait that is not sealed is bound to fail.
You can also use the Seq extractor:
def f(n: Seq[Any]) = n match {
case Nil => "Empty"
case Seq(_*) => "Non-empty"
}
While other answers show correctly an extractor specific for Stream, which is #::, there exists an extractor for Seq (and anything derived from SeqLike) - it is +:, this works for both List (as ::) and Stream (as `#::). With this extractor you can easily write a function which works with both:
def f(n: Seq[Any]) = n match {
case h +: t => "Non-empty"
case _ => "Empty"
}
See also Scala pattern matching on sequences other than Lists
I have written the following Scala code:
case class A(x: Int, out: List[Int])
def isIn: Int => List[Int] => Boolean =
x => l => l.filter { _ == x }.nonEmpty
def filterTest4: (Int, List[A]) => List[List[Int]] = (x, a) =>
for (l <- a; if (isIn(x)(l.out))) yield l.out
The functrion filterTest4 works perfectly fine, however uses the for & yield, which I do not really like and thus would like to see another way. I would be very happy, if someone offered a constructive comment / answer. Please be nice and keep in mind I just started writing in Scala maybe 3 days ago.
I just found the map function, which could be used like this:
def filterTest5: (Int, List[A]) => List[List[Int]] = (x, a) =>
a.filter { a2 => isIn(x)(a2.out) }.map { a2 => a2.out }
I think that does the job.
Alternatively, as other suggested, you can use collect, which combines map and filter. Also you can destructure the case class to access out directly.
def filterTest5: (Int, List[A]) => List[List[Int]] = (x, a) => a.collect {
case A(_, out) if isIn(x)(out) => out
}
I thought PartialFunction can be Monoid. Is my thought process correct ?
For example,
import scalaz._
import scala.{PartialFunction => -->}
implicit def partialFunctionSemigroup[A,B]:Semigroup[A-->B] = new Semigroup[A-->B]{
def append(s1: A-->B, s2: => A-->B): A-->B = s1.orElse(s2)
}
implicit def partialFunctionZero[A,B]:Zero[A-->B] = new Zero[A-->B]{
val zero = new (A-->B){
def isDefinedAt(a:A) = false
def apply(a:A) = sys.error("error")
}
}
But current version Scalaz(6.0.4) is not included it. Is there a reason for something not included ?
Let's shine a different light on this.
PartialFunction[A, B] is isomorphic to A => Option[B]. (Actually, to be able to check if it is defined for a given A without triggering evaluation of the B, you would need A => LazyOption[B])
So if we can find a Monoid[A => Option[B]] we've proved your assertion.
Given Monoid[Z], we can form Monoid[A => Z] as follows:
implicit def readerMonoid[Z: Monoid] = new Monoid[A => Z] {
def zero = (a: A) => Monoid[Z].zero
def append(f1: A => Z, f2: => A => Z) = (a: A) => Monoid[Z].append(f1(a), f2(a))
}
So, what Monoid(s) do we have if we use Option[B] as our Z? Scalaz provides three. The primary instance requires a Semigroup[B].
implicit def optionMonoid[B: Semigroup] = new Monoid[Option[B]] {
def zero = None
def append(o1: Option[B], o2: => Option[B]) = o1 match {
case Some(b1) => o2 match {
case Some(b2) => Some(Semigroup[B].append(b1, b2)))
case None => Some(b1)
case None => o2 match {
case Some(b2) => Some(b2)
case None => None
}
}
}
Using this:
scala> Monoid[Option[Int]].append(Some(1), Some(2))
res9: Option[Int] = Some(3)
But that's not the only way to combine two Options. Rather than appending the contents of the two options in the case they are both Some, we could simply pick the first or the last of the two. Two trigger this, we create a distinct type with trick called Tagged Types. This is similar in spirit to Haskell's newtype.
scala> import Tags._
import Tags._
scala> Monoid[Option[Int] ## First].append(Tag(Some(1)), Tag(Some(2)))
res10: scalaz.package.##[Option[Int],scalaz.Tags.First] = Some(1)
scala> Monoid[Option[Int] ## Last].append(Tag(Some(1)), Tag(Some(2)))
res11: scalaz.package.##[Option[Int],scalaz.Tags.Last] = Some(2)
Option[A] ## First, appended through it's Monoid, uses the same orElse semantics as your example.
So, putting this all together:
scala> Monoid[A => Option[B] ## First]
res12: scalaz.Monoid[A => scalaz.package.##[Option[B],scalaz.Tags.First]] =
scalaz.std.FunctionInstances0$$anon$13#7e71732c
No, this looks good, satisfying both the requirements for (non-commutative) Monoid. Interesting idea. What use case are you trying to support?
Your zero certainly violates the axiom for the identity element, but I think the identity (partial) function would be OK.
Your append also doesn't fulfill the Monoid laws, but instead of orElse you could call andThen (composition). But this would only work for A == B:
implicit def partialFunctionSemigroup[A]: Semigroup[A --> A] = new Semigroup[A --> A] {
def append(s1: A --> A, s2: => A --> A): A-->A = s1 andThen s2
}
implicit def partialFunctionZero[A]: Zero[A --> A] = new Zero[A --> A] {
val zero = new (A --> A) {
def isDefinedAt(a:A) = true
def apply(a:A) = a
}
}
I am trying to do some examples programs in scala to get more familiar with the language, For that I am trying to re-implement some of the built in methods in Haskell, Most of these methods I am sure are also implemented in Scala too, But these are just for my practice. I think I can post some of code snippets (not all of them) to get a better way of doing things and to validate my understanding of scala. So please let me know if this is not the place to do these things.
Here is my scala implementation to get the last element of any list. Is this the right way of doing things, By using Any am I loosing the type of the object containing in the list? Is this how this kind of things implemented in scala?
def getLast(xs: List[Any]): Any = xs match {
case List() => null
case x :: List() => x
case _ :: ys => getLast(ys)
}
Parameterize the type of your function and use "Nil" instead of List() like so:
def getLast[T](xs: List[T]): T = xs match {
case Nil => null.asInstanceOf[T]
case x :: Nil => x
case _ :: ys => getLast(ys)
}
Also, consider making it return an Option type:
def getLast[T](xs: List[T]): Option[T] = xs match {
case Nil => None
case x :: Nil => Some(x)
case _ :: ys => getLast(ys)
}
Usage:
val listOfInts = List(1,2,3)
assert(getLast(listOfInts).isInstanceOf[Int])
val listOfStrings = List("one","two","three")
assert(getLast(listOfStrings).isInstanceOf[String])
Firstly, avoid the null, and especially null.asInstanceOf[T]. Observe the danger with primitives:
scala> null.asInstanceOf[Int]
res19: Int = 0
scala> null.asInstanceOf[Boolean]
res20: Boolean = false
So the signature should either be List[T] => T, whereby last on an empty iterator throws an exception:
def last[T](ts: List[T]): T = ts match {
case Nil => throw new NoSuchElementException
case t :: Nil => t
case t :: ts => last(ts)
}
Or instead: List[T] => Option[T]
def lastOption[T](ts: List[T]): Option[T] = ts match {
case Nil => None
case t :: Nil => Some(t)
case t :: ts => lastOption(ts)
}
def lastOption1[T](ts: List[T]): Option[T] = ts.reverse.headOption
def lastOptionInScala28[T](ts: List[T]): Option[T] = ts.lastOption // :)