Search code examples
javascalaplayframeworkjvmfrege

Is it possible to use frege with Play framework


I'm searching a language for a new project. It's web based project and I want to adopt a REST architecture.

I also want a function programming language. I have the choice between Haskell (because it's cool) and Scala (because of the Play Framework).

After few researches to find out the main differences between this to languages, I have found Frege, an Haskell like language running on the JVM.

So my question is, because Frege is running on the JVM, is it possible to use Play framework with Frege ?


Solution

  • Here is a simple application to demonstrate how we can use Frege with Play. Since Play supports Java, it is actually very easy to use the Java API from Frege even though we don't have native Play support for Frege yet. The application is basically JSON-in and JSON-out. A Frege program reads a parameter from a JSON POST request and responds with a JSON response that greets the user.

    Play conf/routes:

    POST     /greet                      helloplay.FregeApplication.greet()
    

    Now the actual "business logic" in Frege:

    module helloplay.FregeApplication where
    
    import Data.JSON
    import helloplay.Play
    
    data GreetingRequest = GreetingRequest { name :: String }
    
    data GreetingResponse = GreetingResponse { message :: String }
    
    instance FromJSON GreetingRequest where
       fromJSON (Struct fs)  = do
            name <- field "name" fs
            pure $ GreetingRequest name
       fromJSON invalid = fail ("Invalid JSON for Greeting Request: " ++ show invalid)
    
    instance ToJSON GreetingResponse where
        toJSON (GreetingResponse message) = Struct [ assoc "message" message ]
    
    greet :: GreetingRequest -> GreetingResponse
    greet request = GreetingResponse $ "Hello, " ++ request.name
    
    webMain :: Request -> IO ResultStatus
    webMain request = do
      let jsonRequest = parseJSON request.body.asJson.toString
      return $ either badRequest (ok . show . toJSON . greet) jsonRequest
    
    {-
     - This makes the Frege module extend Play Controller class so that it can be configured to handle a route.
     -}
    native module type PlayController where {
        public static play.mvc.Result greet() {
            return frege.runtime.Delayed.forced(
              frege.prelude.PreludeBase.TST.performUnsafe(webMain(request()))
            );
        }
    }
    

    Here we have defined 2 types corresponding to request and response and JSON conversions for those with FromJSON and ToJSON type class instances. The webMain function takes the play Request and reads name from JSON request and returns a JSON response wrapped in play's Result.Status. The webMain function is the one which provides an implementation for a Play controller. A play controller is a class which extends Play's play.mvc.Controller. We can make a Frege module extend a Java class by declaring a native module inside Frege source file. The webMain function is also an IO action so we have to evaluate at some point for something to happen which is what the Java method in the Controller at the bottom does by invoking Frege's ST.performUnsafe and then forcing the result from a possible thunk otherwise the application would just warm up the CPU :)

    Here the types like Request, ResultStatus, PlayController and functions like ok, badRequest are all from Play framework so we have to add native bindings for Frege mentioning their purity or possible null values etc. This is a good thing as Frege is a pure language and doesn't have a notion of null, we have to explicitly mention side effects or Maybe for possible null values for the compiler unlike Scala where you can just call any Java methods.

    The Frege native bindings for Play:

    module helloplay.Play where
    
    data PlayController = native play.mvc.Controller
    
    data Result = pure native play.mvc.Result
    
    data ResultStatus = pure native play.mvc.Results.Status
    
    pure native badRequest play.mvc.Results.badRequest :: String -> ResultStatus
    
    data Request = pure native play.mvc.Http.Request where
      pure native body :: Request -> RequestBody
    
    data RequestBody = pure native play.mvc.Http.RequestBody where
      pure native asText :: RequestBody -> String
      pure native asJson :: RequestBody -> JsonNode
    
    data JsonNode = pure native com.fasterxml.jackson.databind.JsonNode where
      pure native asText :: JsonNode -> String
      pure native toString :: JsonNode -> String
    
    pure native ok play.mvc.Results.ok :: String -> ResultStatus
    

    That is it! We can run it with:

    activator run

    and then

    $ curl --header "Content-type: application/json" --request POST --data '{"name": "PlayFrege"}' http://localhost:9000/greet
    
    {"message" : "Hello, PlayFrege"}
    

    Frege has an SBT plugin that can be used to compile Frege sources in a Play project. I have pushed this sample application in Github if anyone wants to try it out.