I'm experimenting with currying/tupled as an alternative means to pass parameters to a function. In this journey, I'm facing some type difficulties.
I'd like to use implicit conversions to transform a tuple of parameters to the target type. This is what I expect:
Given (REPL example):
case class Cont(value:String) // a simplified container class
val paramTuple = (Cont("One"),Cont("2"))
def operate(s:String, i:Int): String = s+i // my target operation
implicit def contToString(c:Cont): String = c.value
implicit def contToInt(c:Cont): Int = c.value.toInt
//This works - obviously
operate(paramTuple._1, paramTuple._2)
>res4: String = One2
//This is what I want, but doesn't work
(operate _).tupled(params)
<console>:14: error: type mismatch;
found : (Cont, Cont)
required: (String, Int)
Q1: Why the tuple conversion doesn't work? (I'm expecting that an answer to that goes in the lines of: What's required is an implicit conversion between Tuple2[Cont,Cont] to Tuple2[String,Int] but that does not scale up)
Q2: What are my options to have such conversion working?
Keep in mind that I'd like to have a solution for an N-tuple, so defining a specific Tuple2 to Tuple2 is not really a good solution in this case. AFAIK, the magnet pattern works similarly, so I hope there's a way.
Q1: Why the tuple conversion doesn't work? (I'm expecting that an answer to that goes in the lines of: What's required is an implicit conversion between Tuple2[Cont,Cont] to Tuple2[String,Int] but that does not scale up)
Yes, you've got that right.
What are my options to have such conversion working?
You could do it this way:
implicit def liftImplicitTuple2[A, B, A1, B1](tuple: (A, B))
(implicit f1: A => A1, f2: B => B1): (A1, B1) =
(f1(tuple._1), f2(tuple._2))
implicit def liftImplicitTuple3[A, B, C, A1, B1, C1](tuple: (A, B, C))
(implicit f1: A => A1, f2: B => B1, f3: C => C1): (A1, B1, C1) =
(f1(tuple._1), f2(tuple._2), f3(tuple._3))
// etc for tuples as large as you need
Related
Since Scala 2.12 (or is it 2.13, can't be sure), the compiler can infer latent type arguments across multiple methods:
def commutative[
A,
B
]: ((A, B) => (B, A)) = {???} // implementing omitted
val a = (1 -> "a")
val b = commutative.apply(a)
The last line successfully inferred A = Int, B = String, unfortunately, this requires an instance a: (Int, String) to be given.
Now I'd like to twist this API for a bit and define the following function:
def findApplicable[T](fn: Any => Any)
Such that findApplicable[(Int, String)](commutative) automatically generate the correct function specialised for A = Int, B = String. Is there a way to do it within the language's capability? Or I'll have to upgrade to scala 3 to do this?
UPDATE 1 it should be noted that the output of commutative can be any type, not necessarily a Function2, e.g. I've tried the following definition:
trait SummonedFn[-I, +O] extends (I => O) {
final def summon[II <: I]: this.type = this
}
Then redefine commutative to use it:
def commutative[
A,
B
]: SummonedFn[(A, B), (B, A)] = {???} // implementing omitted
val b = commutative.summon[(Int, String)]
Oops, this doesn't work, type parameters don't get equal treatment like value parameters
If at some point some call-site knows the types of arguments (they aren't actually Any => Any) it is doable using type classes:
trait Commutative[In, Out] {
def swap(in: In): Out
}
object Commutative {
def swap[In, Out](in: In)(implicit c: Commutative[In, Out]): Out =
c.swap(in)
implicit def tuple2[A, B]: Commutative[(A, B), (B, A)] =
in => in.swap
}
At call site:
def use[In, Out](ins: List[In])(implicit c: Commutative[In, Out]): List[Out] =
ins.map(Commutative.swap(_))
However, this way you have to pass both In as well as Out as type parameters. If there are multiple possible Outs for a single In type, then there is not much you can do.
But if you want to have Input type => Output type implication, you can use dependent types:
trait Commutative[In] {
type Out
def swap(in: In): Out
}
object Commutative {
// help us transform dependent types back into generics
type Aux[In, Out0] = Commutative[In] { type Out = Out0 }
def swap[In](in: In)(implicit c: Commutative[In]): c.Out =
c.swap(in)
implicit def tuple2[A, B]: Commutative.Aux[(A, B), (B, A)] =
in => in.swap
}
Call site:
// This code is similar to the original code, but when the compiler
// will be looking for In it will automatically figure Out.
def use[In, Out](ins: List[In])(implicit c: Commutative.Aux[In, Out]): List[Out] =
ins.map(Commutative.swap(_))
// Alternatively, without Aux pattern:
def use2[In](ins: List[In])(implicit c: Commutative[In]): List[c.Out] =
ins.map(Commutative.swap(_))
def printMapped(list: List[(Int, String)]): Unit =
println(list)
// The call site that knows the input and provides implicit
// will also know the exact Out type.
printMapped(use(List("a" -> 1, "b" -> 2)))
printMapped(use2(List("a" -> 1, "b" -> 2)))
That's how you can solve the issue when you know the exact input type. If you don't know it... then you cannot use compiler (neither in Scala 2 nor in Scala 3) to generate this behavior as you have to implement this functionality using some runtime reflection, e.g. checking types using isInstanceOf, casting to some assumed types and then running predefined behavior etc.
I'm not sure I understand the question 100%, but it seems like you want to do some kind of advanced partial type application. Usually you can achieve such an API by introducing an intermediary class. And to preserve as much type information as possible you can use a method with a dependent return type.
class FindApplicablePartial[A] {
def apply[B](fn: A => B): fn.type = fn
}
def findApplicable[A] = new FindApplicablePartial[A]
scala> def result = findApplicable[(Int, String)](commutative)
def result: SummonedFn[(Int, String),(String, Int)]
And actually in this case since findApplicable itself doesn't care about type B (i.e. B doesn't have a context bound or other uses), you don't even need the intermediary class, but can use a wildcard/existential type instead:
def findApplicable[A](fn: A => _): fn.type = fn
This works just as well.
I've been wondering, why this piece of code won't compile?
Is there a way in Scala to create method/func that is generic parametrised and allows for such operation like 'reduce'.
Is this behaviour having anything in common with type erasure or is it something else? I would love to see broad explanation of this :)
def func2[B <: Int](data: Seq[B]): Unit = {
val operation = (a: B, b: B) => a.-(b)
data.reduce(operation)
}
Compiler says:
type mismatch;
found : (B, B) => Int
required: (Int, Int) => Int
Also, in same spirit - is it possible overall to call any 'stream-like' method, on parametrized collection with this method:
def func2[B <: Int](data: Seq[B]): Unit = {
val operation = (a: B, b: B) => a.-(b)
data.sum
}
also gives:
could not find implicit value for parameter num: Numeric[B]
The result of a.-(b) is always Int and your operation function is (B, B) => Int. But reduce expects a (B, B) => B function.
def reduce[A1 >: A](op: (A1, A1) => A1): A1
So an (Int, Int) => Int function is the only one option for the compiler because of Int result type of operation.
This variant compiles:
def func2[B <: Int](data: Seq[B]): Unit = {
val operation = (a: Int, b: Int) => a.-(b)
data.reduce(operation)
}
Numeric isn't covariant. Its interface is Numeric[T]. Hense Numeric[B] isn't subclass of Numeric[Int] for B <: Int and there is no implicit Numeric[B].
Why I can't put upper types bounds on type of collection, and assume, that type B (with that constraint) just has these methods I need?
Your assumption is correct. Your upper bound on B makes the following compile
val operation = (a: B, b: B) => a.-(b)
And also makes reduce available on a Seq[B], because Seq is covariant.
Since compiler knows that "B ISA Int", the - method exists on it. However, it's still going to return an Int. Because the signature of + restricts the return type to Int
def +(x: Int): Int
The reduce operation can understand only one type. So if you have
reduce[B](operation)
It will expect operation to be of type (B,B) => B
And if you have
reduce[Int](operation)
It will expect operation to be of type (Int,Int) => Int
One of the things you can do is
val operation = (a: Int, b: Int) => a - b
This is safe because your B is always also an Int
this works
def func2[B](data: Seq[B], f: (B, B) => B): Unit = {
val operation = (a: B, b: B) => f(a, b)
data.reduce(operation)
}
It is not really clear what you are trying to achieve.
First of all restriction B <: Int makes no sense as Int is a final class in Scala.
Secondly, using reduce together with - also makes no sense because - is not commutative. This is important because reduce unlike reduceLeft/reduceRight or foldLeft/foldRight does not guarantee the order of the evaluation. Actually
def reduce[A1 >: A](op: (A1, A1) => A1): A1 = reduceLeft(op)
is as valid default implementation as
def reduce[A1 >: A](op: (A1, A1) => A1): A1 = reduceRight(op)
but obviously they will produce different results for - operation.
From a higher level point of view it looks like something similar to what you want to achieve can be done using type classes and particularly Numeric. For example you could have a method like this:
def product[B: Numeric](data: Seq[B]): B = {
val numeric = implicitly[Numeric[B]]
data.reduce(numeric.times)
}
Note that multiplication is commutative so it is a reasonable implementation. Actually this is almost how sum and product are implemented in the standard library. The main difference is that the real implementation uses foldLeft which allows defining the default value for an empty Seq (0 and 1 respectively)
why do i need to add the type annotation at the first line?
c.get[List[String]]("primary-group") is Decoder.Result[List[String]] after flatMap it should keep the top type and be Decoder.Result[String] but it changes to Either[DecodingFailure, String]. Why? Is the problem that it is dependent type?
case class JWTPayload(primaryGroup: Group, groupMember: List[Group], name: String, pid: String)
implicit val jwtPayloadDecoder: Decoder[JWTPayload] = Decoder.instance(c =>
(
c.get[List[String]]("primary-group").flatMap(l => if(l.size == 1) l.head.asRight else DecodingFailure("", c.history).asLeft) : Decoder.Result[String],
c.get[List[String]]("group-member"),
c.get[String]("name"),
c.get[String]("pid")
).map4(
JWTPayload
)
)
Without : Decoder.Result[String I get
Error:(43, 7) value map4 is not a member of (scala.util.Either[io.circe.DecodingFailure,String], io.circe.Decoder.Result[List[String]], io.circe.Decoder.Result[String], io.circe.Decoder.Result[String])
possible cause: maybe a semicolon is missing before `value map4'?
).map4(
Thanks
This is not a full answer but I hope it will provide some insights. The crucial part here is how map4 is implemented. As of cats 0.9 it is done via cats.syntax.TupleCartesianSyntax trait and its implicit catsSyntaxTuple4Cartesian which wraps a 4-tuple into a cats.syntax.Tuple4CartesianOps class (in cats 1.0 "cartesian" was changed to "semigroupal"). This code is auto-generated for all tuples up to 22 by Boilerplate.scala. The auto-generated code looks something like this:
implicit def catsSyntaxTuple4Cartesian[F[_], A0, A1, A2, A3](t4: Tuple4[F[A0], F[A1], F[A2], F[A3]]): Tuple4CartesianOps[F, A0, A1, A2, A3] = new Tuple4CartesianOps(t4)
private[syntax] final class Tuple4CartesianOps[F[_], A0, A1, A2, A3](t4: Tuple4[F[A0], F[A1], F[A2], F[A3]]) {
def map4[Z](f: (A0, A1, A2, A3) => Z)(implicit functor: Functor[F], cartesian: Cartesian[F]): F[Z] = Cartesian.map4(t4._1, t4._2, t4._3, t4._4)(f)
def contramap4[Z](f: Z => (A0, A1, A2, A3))(implicit contravariant: Contravariant[F], cartesian: Cartesian[F]): F[Z] = Cartesian.contramap4(t4._1, t4._2, t4._3, t4._4)(f)
def imap4[Z](f: (A0, A1, A2, A3) => Z)(g: Z => (A0, A1, A2, A3))(implicit invariant: Invariant[F], cartesian: Cartesian[F]): F[Z] = Cartesian.imap4(t4._1, t4._2, t4._3, t4._4)(f)(g)
def apWith[Z](f: F[(A0, A1, A2, A3) => Z])(implicit apply: Apply[F]): F[Z] = apply.ap4(f)(t4._1, t4._2, t4._3, t4._4)
}
Note the F[_] (functor) type parameter. Effectively this code adds map4 method to any 4-tuple where each inner type is the same functor over some types.
So assuming you did import cats.implicits._, after (partial) implicits resolution your code is actually something like this:
cats.implicits.catsSyntaxTuple4Cartesian[Decoder.Result, String, List[String], String, String](
c.get[List[String]]("primary-group").flatMap(l => if (l.size == 1) l.head.asRight else DecodingFailure("", c.history).asLeft): Decoder.Result[String],
c.get[List[String]]("group-member"),
c.get[String]("name"),
c.get[String]("pid")
).map4[JWTPayload](
JWTPayload
)
When you don't specify Decoder.Result[String], Scala compiler is not smart enough to get that it should split Either[DecodingFailure, String] into a functor type Either[DecodingFailure, _] and String and then
there will be matching Functor and Cartesian implicit objects (actually provided by the cats.implicits object via cats.instances.AllInstances and cats.instances.EitherInstances traits)
it would match the functor type used for other 3 fields in the tuple (i.e. Decoder.Result[_]).
So I think this behavior is a result of a combination of the fact that map4 is added to 4-tuple via an implicit Ops-class and the fact that the underlying type is Either which is 2-places generic type rather than a simple Functor.
I'm learning Scala, and find from scala doc the definition of PartialFunction and Function1, as shown below:
trait PartialFunction[-A, +B] extends (A) ⇒ B
trait Function1[-T1, +R] extends AnyRef
Q1) My 1st question is: what's the type of (A) => B?
And, I know we can turn the PartialFunction to be a normal function by the lift method.
But Q2) what's the relationship between ParitialFunction and Function1?
It seems if some function parameter is of type Function1, we can pass a matching PartitionFunction to it, as shown below:
scala> val process = (f: Function1[String, Int]) => f("1024")
process: (String => Int) => Int = <function1>
scala> val pattern = "([0-9]+)".r
pattern: scala.util.matching.Regex = ([0-9]+)
scala> val str2int: PartialFunction[String, Int] = {
| case pattern(num) => num.toInt
| }
str2int: PartialFunction[String,Int] = <function1>
scala> accept(str2int)
res67: Int = 1024
Thanks!
A ⇒ B is syntax sugar for Function1[A, B]. Similarly, (A1, A2) ⇒ R is actually Function2[A1, A2, R], etc. all the way to 22 (completely arbitrary limit). The definition of PartialFunction is thus
trait PartialFunction[-A, +B] extends Function1[A, B]
Since a PartialFunction[A, B] is also a Function1[A, B], you can pass it into something that wants an A ⇒ B. The only reason we use ⇒ over FunctionN is aesthetic: it looks nicer. Indeed, since ⇒ isn't really a type name, we can't say something like:
type ApIntInt[T[_, _]] = T[Int, Int]
// ApIntInt[⇒] // Error: ⇒ is not a type and was not expected here
ApIntInt[Function1] // Fine: Function1 is a type, it has the right kind, so it works.
// ApIntInt[Function1] = Function1[Int, Int] = Int ⇒ Int
Since you're a beginner, you won't see this kind of stuff (higher kinds) for a long time yet, but it is there, and you may well hit it someday.
When you use a PartialFunction as a Function1, if you pass a value at which it is not defined, it (probably) throws an exception, which is usually a MatchError (but doesn't have to be). By contrast, if you call pf.lift, that creates a Function[In, Option[Out]], which returns Some(result) if the PartialFunction is defined at a point, and returns None if it isn't, per the Scaladoc.
Ex:
lazy val factorial: PartialFunction[Int, Int] = {
case num if num > 1 => num * factorial(num - 1)
case 1 => 1
}
assert(!factorial.isDefinedAt(0))
factorial.apply(0) // Using a PF as a Function1 on an undefined point breaks (here with MatchError)
factorial.lift.apply(0) // This just returns None, because it checks isDefinedAt first
I have the following method that I would like to apply fold operation on:
def rec(id: String, elems: Seq[(String, MyCase)]) = {
elems.fold(Seq.empty[(String, Seq[String])] { elem =>
....
}
}
What I do not get is the type of the elem is Nothing and I do not understand why it should be! Any clues?
You are missing the closing parentheses before the {, that's why your IDE, probably, thinks, the type is Nothing.
Also, you are, probably, looking for foldLeft, not fold (the first parameter of the latter must match the type of elements of the sequence).
Now the (simplified) signature of .foldLeft on Seq[A] is:
foldLeft[B](b: B)(f: (B,A) => B)
As you can see, it takes a function, that transforms a Tuple2 into the type of the first parameter. The first element of the tuple has the same type as the first param, the second element is the same type as the elements of the sequence.
In your example, B is Seq[(String, Seq[String])], and the sequence elements are (String, MyCase). The type of input to the function would therefore take a horribly looking tuple like this:
(Seq[(String, Seq[String])], (String, MyCase))
This is caused by you are not only want to fold, you also you want to map the tuple, see the fold method signature:
def fold[A1 >: A](z: A1)(op: (A1, A1) => A1): A1 = foldLeft(z)(op)
The input type and outtype must be same: A1,
so if want to map, you maybe want to try foldLeft:
def foldLeft[B](z: B)(op: (B, A) => B): B =
There is a generics for output type B without bounding to A.
also we can find the fold source code is calling:
def fold[A1 >: A](z: A1)(op: (A1, A1) => A1): A1 = foldLeft(z)(op)
use foldLeft maybe like:
elements.fold(Seq.empty[(String, Seq[String])])((a, b) => a ++ Seq((b._1, Seq[String]())))
2nd EDIT:
I'm an idiot. A fold must return the same value as the input type.
Replace all the below folds with foldLefts for example and it works.
You can't transform the data type in a standard fold.
It was compiling for me but I didn't notice the return type was useless.
The second parameter in a fold is a function:
(ResultType, SingleElement) => Result Type
In this case
(Seq[(String, Seq[String])], Seq[(String, MyCase)]) => Seq[(String, Seq[String])]
Your code only has one input on the second parameter so the compiler doesn't know what it is. So it should look something like:
elems.foldLeft(Seq.empty[(String, Seq[MyCase])] {(zeroSeq, nextElem) =>
//zeroSeq: Seq.empty[(String, Seq[MyCase]
//nextElem: (String, MyCase)
}
EDIT:
The following for example compiles:
case class MyCase(x: String)
val elems: Seq[(String, MyCase)] = Seq(("Hi", MyCase("B")))
elems.foldLeft(Seq.empty[(String, Seq[MyCase])]){(z, s) =>
z
}