Get information of type in Scala3 macro
Asked Answered
O

3

1

I'm struggling to get information of type in Scala3 macro implementation. I'll explain problem through code.

Here is application logic:

object BlockServiceImpl extends BlockService:
  def authenticateUser0() = new ServiceCall[AuthUser,AuthUserResponse]:
     def invoke(request: AuthUser): Future[AuthUserResponse] = 
       println("BlockServiceImpl authenticateUser0 called")
       Future.successful(AuthUserResponse("test"))   

Now, for the logic I want to make endpoints with help of macro.

defineRoute("POST","/v1/block",BlockServiceImpl.authenticateUser0)

This is inline method:

inline def defineRoute[Q: RootJsonFormat ,R: RootJsonFormat](method: String, uri: String,inline call: () =>  ServiceCall[Q,R]): AkkaHttpCall = ${ methodImpl[Q,R]('uri, 'call)}

And this is implementation of macro:

def methodImpl[Q: Type,R: Type](uri: Expr[String],expr: Expr[Function0[ServiceCall[Q,R]]])(using ctx: Quotes): Expr[AkkaHttpCall] = ...

How can I get information that Q is AuthUser type during macro expansion in a compile time?

Outhouse answered 6/5, 2021 at 16:52 Comment(7)
Q: Type, R: Type you have no bounds on your methodImpl, can you bind them accordingly? (I'm not super familiar with the new macros, but how can you enforce a certain type if the type has no bounds?)Grammar
How to bind them?Outhouse
I think using upper bound : Q <: AuthUser : Type.Edwardedwardian
There will be different defineRoute calls with different Q type.Outhouse
@Outhouse Type bounds can be extremely useful if the type hierarchy is well defined. For the same or sub-classes, we can use an "upper bound" or "[T <: R]". For the same or super-classes, we can use a "lower bound" or "[T >: R]". We can bind both ends simultaneously using "[T >: R <: W]". I've used this as a reference in the past: blog.knoldus.com/scala-type-boundsGrammar
Considering that several of your base types are not provided, specifically addressing your actual implementation issues are not possible without guessing. "Black-Box Types" AKA "Types that are givens for you but are not necessarily known to me": BlockService, ServiceCall, AuthUser, AuthUserResponse, RootJsonFormat , AkkaHttpCallGrammar
@RandomnessSlayer, yes I understand the idea, but it can be any case class/object, they are request/response actually. I just wanted to know if user used special cases for request/response (NotUsed, Done, etc). So, that information I put in serializers. Lagom framework implemented it in that way.Outhouse
O
0

I've solved it by putting information if Q and R were special cases (NotUsed, Done,..) in serializers for Q and R. The idea is took from the Lagom framework.

Outhouse answered 11/5, 2021 at 10:34 Comment(1)
Your own answer contains zero technical information to help another person solve similar problems. You've basically accepted an answer saying "I've fixed it myself using a framework called Lagom" which, without context, is not helpful to fix a similar but not exactly the same value. Which is the purpose of StackOverflow questions and answers, to be able to understand the why or mechanisms to the question/problem at hand.Grammar
E
2

A possible solution could be to use pattern matching on quoted expressions.

So for example, you can define a method that is used to retrieve the compile-time type:

def tag[A <: AnyKind] = throw new IllegalStateException("use it only to pattern match types")

And then, in the macro expansion, you can perform pattern match as:

'{ tag[Q] } match {
      case '{ tag[AuthUser] } => // here I am sure that Q is AuthUser, since Q is matched with AuthUser
}

It is quite a trick (and is not very extensible as you have to add each type) so take everything I say with a grain of salt... I think that exists a clearer solution depends on your particular application logic :)

Edwardedwardian answered 7/5, 2021 at 10:3 Comment(4)
This could work. I don't need to figure out information for every type but just for one special case. I will try it later.Outhouse
I've solved it differently. I've added information for special type in serializers. So, in a serializer I know if it were NotUsed,Done.Outhouse
Good, why you don't answer the question yourself so, in the future, other people have a possible solution for this problem? :)Edwardedwardian
I forget it :).Outhouse
G
1

Bounding (See: Scala 3 Book: Context Bounds) the type of a parameter in a function can be achieved in several ways.


THIS IS WRONG (TY Dmytro!): When using generic parameters like so: [T : Type] we are aliasing a type

CORRECTION: When using generic parameters like so: [T : R] we are using syntactic sugar which represents an implicit parameter of type R[T]


For many applications, including yours, it can be beneficial to restrict the type of our generic parameter.


There are two main bounds, an "upper" and a "lower" bound.

The "upper" bound e.g. [T <: U] specifies that T must be of type U, or a subclass of U

The "lower" bound e.g. [T >: U] specifies that T must be of type U, or a super-class of U

It is possible to restrict both bounds, by first specifying the lower bound then the upper bound, e.g. [T >: Cat <: Animal]

Grammar answered 8/5, 2021 at 2:18 Comment(1)
"When using generic parameters like so: [T : Type] we are aliasing a type" Context bounds do not alias a type.Kershner
O
0

I've solved it by putting information if Q and R were special cases (NotUsed, Done,..) in serializers for Q and R. The idea is took from the Lagom framework.

Outhouse answered 11/5, 2021 at 10:34 Comment(1)
Your own answer contains zero technical information to help another person solve similar problems. You've basically accepted an answer saying "I've fixed it myself using a framework called Lagom" which, without context, is not helpful to fix a similar but not exactly the same value. Which is the purpose of StackOverflow questions and answers, to be able to understand the why or mechanisms to the question/problem at hand.Grammar

© 2022 - 2024 — McMap. All rights reserved.