Type Mismatch trying to make a session modification function reusable

This compiles:

.exec( _.set( “foo”, “bar” ) )

This does not:

def foobar = ( Session : Session ) = _.set( “foo”, “bar” )

.exec( foobar )

It is telling me that foobar is of the wrong type. It says it expects one of ActionBuilder, Execs[], Iterable[Execs[]], or session.Expression[Session].

Exactly what am I doing wrong? Am I missing an import, maybe?

This is 3.2.1

Hi,

Did you try with a Validation[Session] at the end ? An Expression[T] is a just a Session => Validation[T]
You could force implicits by specifying the return type of foo :

def foo: Session => Validation[Session] = _.set(“key”, “value”)

Yes, I tried setting it to Expression[Session]. But the fact that the first code compiles means that I shouldn’t have to. I am more interested in understanding the inner workings of Scala, and WHY the one works but the other doesn’t. :slight_smile:

Oh,

In the background, there is an implicit function that transforms the return of set in the first example to a Success of Session, which is one of the possible implementation of Validation. This way, Gatling gets the type its supposed to have at this point.

In the first example, the implicit conversion happens because Scala knows that exec actually expect a Validation[T] at the end, so it does the conversion for you.

In the second example, as the function is put elsewhere–in its own function, Scala can’t put an implicit anywhere because:

  1. foobar alone doesn’t tell the compiler it actually wants a Validation[T] there, so there is nothing more to do
  2. .exec(foobar) needs one but if a transformation would occur there, it would mean surrounding the whole function foobar with a Validation[T]. Hence, we would end up with a Validation[Session => Session] and not a Session => Validation[Session]

Does that make sense to you?

Wait, so does Scala’s ability to add implicit conversions only work on inline expressions, and not on variables or return values from function calls?

This works:

http( “bar” )

This does not:

val foo = “bar”
http( foo )

Mind blown.

I’m sure there is a good reason why they don’t support applying implicits in that situation. You wouldn’t happen to know why, would you?

This works:

http( “bar” )

This does not:

val foo = “bar”
http( foo )

Yes it does: https://scastie.scala-lang.org/0sqfaXDTTOmjId0plupR6w

Actually, it can!

foo being a String, Scala still has the ability to convert it to a Expression[String] afterwards

When you wrote this:

.exec(_.set(“foo”, “bar”))

As _ is just to shortcut to:

.exec(session => session.set(“foo”, “bar”))

Scala can apply a implicit conversion directly as exec is asking for a Expression[Session] as an input:

.exec(session => Success(session.set(“foo”, “bar”)))

The second example was:

def foobar = (session: Session) => _.set(“foo”, “bar”)

exec(foobar)
^ here we are learning about the needed conversion to a Expression[Session]

// If we convert here:
exec(Success(foobar))

// We end up with a Validation[Session => Session]
// While we wanted a Session => Validation[Session]
// Which is the definition of Expression[Session]
// We would need to inline the function and apply the implicit afterwards, hence rewriting the code tree

foobar is a function. Scala still has the ability to apply implicit conversions. But, as the conversion happens after the function reference is put inside an exec, we would end up with the wrong type: Validation[Session => Session], instead of Session => Validation[Session] which is precisely what a Expression[Session] is.

You could also look at it the other way around: an implicit is a function that describes how to convert a type A to a type B and do the conversion for you when you put a variable A inside a function that needs a B.

exec ask for a Expression[Session]
http, get, post and so asks for a Expression[String]
doIf for a Expression[Boolean]
etc.

We define implicits for String, Boolean, Int, Session and others.

When you put a String inside a function that actually expect an Expression[String], the compiler will do the conversion for you.
Same for exec if the type is properly defined inside the function, or if the function is inlined.

However, we do not define a conversion for Session => Session, so the compiler stops there when the function is defined as Session => Session without anything Validation anywhere.

I’m still a little confused: You have an implicit to convert a string to an Expression[String], right? So why doesn’t this work:

var foo = “bar”
http( foo )

It does!

2019-09-19-1568904116_screenshot.png

foo and foo3 works
foo2 and foo4 does not

Then why does my IDE complain about it? What am I missing?

Most likely explanation: you’ve messed up with Gatling imports.

You should have only import io.gatling.core.Predef._ and import io.gatling.http.Predef._ and absolutely not reimport our implicits or they’ll be imported twice and not work.

Something must be wrong with my IDE, then. How frustrating. sigh

CRAP! I confused myself with an invalid test:

exec( http(foo) ) // doesn’t compile
exec( http(foo).get(foo) ) // DOES!!!

Clearly, the http object is not the right type until after you have called one of the method functions. Oops!

Okay, to recap, the reason it didn’t work with a string is because of PEBCAC. My mistake.

I’m still trying to wrap my brain around why it doesn’t work with the function. Let’s see:

val foo : ( Session => Session ) =
(session) => session.set( “foo”, “bar” )
exec( session => session.set( “foo”, “bar” ) ) // works
exec( foo ) // fails

foo is an expression of type (Session=>Session). The parameter to exec that works is also a lambda expression that takes in a session and returns a session. But somehow, Scala knows to transform that one.

Of course, it only knows that session is a Session because it knows that exec has a form that takes in an Expression[Session], which is a type alias for ( Session => Validation[Session] ). So as it is compiling, it infers the correct type for session, and infers the expected type for the result, which is Validation[Session]. There must be an implicit somewhere that wraps the session in a Success(), and so it is able to make the necessary conversion at compile time.

Whereas, the type of foo is (Session=>Session). The only way to convert that is to create a wrapper function that calls the function, and wraps the result in a Success(). Since there is no such wrapper available implicitly, the Compiler complains.

So, in my head, applying the substitution principle, it should work. But when you start throwing implicit conversions in there, things don’t work quite the same. Is that why there is a general warning against the indiscriminate use of implicit conversions? Makes the code harder to reason about?

Thanks for helping me understand. :slight_smile:

You got it!

Is that why there is a general warning against the indiscriminate use of implicit conversions? Makes the code harder to reason about?

Exactly! It makes things kind of magic and doesn’t make it easy to understand what is going on.