Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[eta] Annotations and ADT foreign export #601

Open
NickSeagull opened this issue Jan 4, 2018 · 10 comments
Open

[eta] Annotations and ADT foreign export #601

NickSeagull opened this issue Jan 4, 2018 · 10 comments
Assignees
Labels

Comments

@NickSeagull
Copy link
Collaborator

NickSeagull commented Jan 4, 2018

There are many frameworks that depend on annotations. I will be using Spring Boot as an example.

Spring Boot is a widespread Java framework that does not enforce any kind of structure, while being able to use all the Spring Boot modules to make application development easier.

These modules include interoperability with:

  • CassandraDB
  • CouchDB
  • Kafka
  • Many caching systems
  • Queueing systems
  • Mail
  • And many more

The problem here is, Spring Boot is mainly annotation driven. And while we are able to do dependency injection using a custom environment (like a ReaderT), other features of the framework are not accessible because of its hard dependency on annotations.

Of course, one can use the Java Persistence API (JPA) to handle the creation and modification of database entries.

Example application

This is an example of a very simple Spring Boot application that handles persistence and routing:

I'll be using Scala as the example language just for the sake of brevity, as it is less verbose and it is directly translatable to Java.

Application.scala

@SpringBootApplication
class Application { }

def main(args: Array[String]) = {
  SpringApplication.run(classOf[Application], args)
}

This is the entry point of our application. We are passing Application.class to the run method so Spring can operate on it using reflection.

I can imagine doing this easily in Eta:

@SpringBootApplication
data Application = Application @eta.myexample.Application

main :: IO ()
main = do
    args <- getArgs
    runSpringApplication (getClass (Proxy :: Proxy Application)) args

Post.scala

@Entity
class Post {
  @Id
  @GeneratedValue
  var id: Long = null
  var title: String = null
  var content: String = null
}

This class defines an entity that will be stored in the Post table.

I think it would be quite tricky to do so, unless we force the exported ADTs to be records:

@Entity
data Post = Post
    { id :: @Id @GeneratedValue (Mutable Long)
    , title :: Mutable String
    , content :: Mutable String
    }

(Idea: non-records fields could be exported as _1, _2, _n)

PostRepository.scala

trait PostRepository extends JpaRepository[Post, Long] { }

This is just an interface that allows us to use some methods to access our database:

data PostRepository = PostRepository @foo.PostRepository

type instance Inherits PostRepository = '[JpaRepository Post Long]

(Note: I'm not sure if this is the correct way to do so)

PostController.scala

@Controller
@RequestMapping("/")
class PostController {

  @Autowired
  var postRepository: PostRepository = null

  @GetMapping("/")
  def posts(model: Model): String = {
    val posts = postRepository.findAll()
    model.addAttribute("posts", posts)
    "postList"
  }

  @PostMapping("/")
  def addToPostsList(post: Post): String = {
    postRepository.save(post)
    "redirect:/"
  }

}

This is the biggest part, the controller that handles all the requests and uses the other classes

@Controller
@RequestMapping "/"
data PostController = PostController
  { postRepository :: @Autowired (Mutable PostRepository)
  }

@GetMapping "/"
posts :: Model -> Java PostController String
posts model = do
  posts <- postRepository <.> findAll
  model <.> addAttribute "posts" posts
  return "postList"

@PostMapping "/" 
addToPostsList :: Post -> Java PostController String
addToPostsList post = do
  postRepository <.> save post
  return "redirect:/"

It looks quite feasible to me.

Note: I'm not an expert in the Eta export features. Although I'm a Haskeller, I haven't used Eta too much :)

**Note 2: This are just snippets of code, if the whole project is needed, you can find it in this repository, this commit **

@rahulmutt
Copy link
Member

Thanks for the beautiful write-up, @NickSeagull!

I agree, this looks quite feasible. I made the following edits to your original issue:

  • I moved the annotations into the types instead of the identifiers. Since types describe a value, we can treat annotations as types that just add more context, but are effectively treated as comments by the typechecker. So @Autowired PostRepository ~ PostRepository as far as the typechecker is concerned. Only the foreign export generator in the compiler concerns itself with the annotations.

  • I added a Mutable type constructor in front of all the potentially mutable fields. Field with a Mutable identifier generate record selectors with slightly different type signatures:

    title :: Mutable String

    would generate

    title :: Post -> IO String

    instead of the usual

    title :: Post -> String

    to ensure proper ordering of effects. I still need to think over how to work out the syntax for updating such a mutable field in a record, as the existing record syntax won't cut it. Ideas?

  • I got rid of the foreign export data because the fact that you're adding annotations to a data declaration automatically indicates that you're intending to export it (there's no other reason to do so). No need for extra typing.

    We're eventually gonna get rid of foreign export keywords altogether and make something more clean. We were originally trying to keep in tune with GHC, but we've already made so many syntactic changes that it doesn't make sense to stick with verbose syntax.

  • I updated your code to the new FFI syntax which is a lot less scary.

@rahulmutt rahulmutt reopened this Jan 4, 2018
@rahulmutt rahulmutt added the ffi label Jan 4, 2018
@NickSeagull
Copy link
Collaborator Author

NickSeagull commented Mar 3, 2018

I might start working on this, what would be the first steps to work on this @rahulmutt ?

Also, could you assign this to me please? 😄

@NickSeagull
Copy link
Collaborator Author

NickSeagull commented Mar 3, 2018

Also, I saw that the Lexer.x file has a Java annotation declaration. Is that related?

@rahulmutt
Copy link
Member

rahulmutt commented Mar 4, 2018

@NickSeagull Yes, the java annotation declaration was made keeping in mind that we'd eventually support annotations for exports. Right now that is used for parsing the @ notation for JWTs, so you can re-use it when making parsing rules for annotations.

The general idea of how to go about implementing this:

  • Annotations should not exist at the Core level, since annotations don't affect the code-generation process. Annotations should exist at the HsSyn level and should disappear as soon as the foreign export generator (which lives inside the desugarer in ETA.DeSugar.DsForeigns). The first part of this issue is updating the HsSyn syntax tree to store the annotations to make them available to the export generator.
  • The lexer or parser should be modified to store consecutive annotations into a group and as soon as the next binding definition comes up, stash the stored consecutive annotations into the binding. This is different from how Haskell normally handles things like {-# INLINE [binder] #-} because in that case, the [binder] is specified so we know what the annotation applies to. In this case, we only know it will apply to the next binding definition.
  • And I take back what I said above - we will have a special annotation @EXPORT() that marks the function to be exported. The EXPORT annotation will have parameters equivalent to the "export string" part of normal foreign export java declarations so that you can specify how it is exported.
  • The way you parse foreign exports now changes - instead of looking for the key words foreign export java ... you look for an annotation sequence that starts with @EXPORT and parse the successive binding to get the type signature.

@rahulmutt
Copy link
Member

By the way you should parse annotations exactly like Java does (keeps things consistent): https://docs.oracle.com/javase/specs/jls/se7/html/jls-18.html

@rahulmutt
Copy link
Member

Annotations should not exist at the Core level, since annotations don't affect the code-generation process. Annotations should exist at the HsSyn level and should disappear as soon as the foreign export generator (which lives inside the desugarer in ETA.DeSugar.DsForeigns). The first part of this issue is updating the HsSyn syntax tree to store the annotations to make them available to the export generator.

Disregard this entire comment.

Because annotations are only relevant for foreign exports, you need only extend the ForeignExport decl:
https://github.com/typelead/eta/blob/master/compiler/ETA/HsSyn/HsDecls.hs#L1331-L1334
You can look into the ForeignExport type and add a field to store Java annotations.

@NickSeagull
Copy link
Collaborator Author

Great, thanks! 😄

@NickSeagull NickSeagull changed the title RFC - Annotations and ADT foreign export Annotations and ADT foreign export Mar 6, 2018
@NickSeagull NickSeagull changed the title Annotations and ADT foreign export [eta] Annotations and ADT foreign export Mar 7, 2018
@rahulmutt
Copy link
Member

I've started working on this.

I've implemented:

  • Support for annotations in codec-jvm
  • Allow the code below to parse:
@Controller
@RequestMapping "/"
data PostController = PostController
  { @Autowired postRepository :: PostRepository
  }

@GetMapping "/"
posts :: Model -> Java PostController String
posts model = do
  posts <- postRepository <.> findAll
  model <.> addAttribute "posts" posts
  return "postList"

@PostMapping "/" 
addToPostsList :: Post -> Java PostController String
addToPostsList post = do
  postRepository <.> save post
  return "redirect:/"

Remaining steps:

  • Implement support for mutable fields, taking inspiration from the GHC Proposal. The implementation will be done in a way that is not FFI-specific and can be used for normal ADTs too.
  • Fully flesh out the parameters that @export can take and figure out all the different configuration options to expose to the user.
  • Send the annotation information down the compilation pipeline and stash them in the classfile.

@Escapingbug
Copy link

Any progress on this? Looking forward to it.

@rahulmutt
Copy link
Member

No unfortunately. Thanks for showing interest though, I'll pin this issue to indicate that it has priority.

@rahulmutt rahulmutt pinned this issue Apr 23, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants