Skip to content

dancewithheart/agda2scala

Repository files navigation

CI

An experimental Agda backend that generates Scala 2 or Scala 3 source files.

Currently supported:

  • ✅ Sum types / ADTs
    • ✅ simple ADTs
    • ✅ polymorphic ADTs, e.g. Maybe[A], List[A]
    • ✅ constructors with arguments
  • ✅ Product types / records
    • ✅ simple records
    • ✅ records with polymorphic parameters
  • ✅ Simple and polymorphic functions, e.g. id[A]
  • ✅ Literals: Int / Nat, Bool, String (subset)
  • ✅ Function application
    • ✅ normal visible arguments
    • ✅ erased hidden/type-level Agda arguments
  • ✅ Pattern matching on constructors
    • ✅ zero-arity constructors
    • ✅ flat constructor patterns with arguments
    • ✅ constructor branch binders
    • 🚧 nested constructor patterns
    • 🚧 literal branches / catch-all branches
  • ✅ Basic conditionals and operators
    • ✅ Agda if_then_else_ lowered to Scala if ... then ... else ...
    • ✅ selected binary operators
    • ✅ natural-number boolean comparison _ <ᵇ _ lowered to Scala <
  • ✅ 🚧 Mapping selected Agda stdlib names to Scala stdlib-like output
    • Nat
    • List
    • ✅ pairs / products
    • 🚧 broader stdlib coverage

Roadmap is captured in milestones:

  • support polymorphic ADTs: List, Red Black Tree, ZIO: #17
  • support FP abstractions: Functor, Monad: #12

Basic usage:

cabal run -- agda2scala ./examples/adts.agda

compile following Agda code:

module examples.rbt where

open import Data.Bool using (Bool; true; false; if_then_else_)
open import Data.Nat using (ℕ; _<ᵇ_)

data Color : Set where
  Red Black : Color
{-# COMPILE AGDA2SCALA Color #-}

data RedBlackTree (V : Set) : Set where
  EmptyRBT : RedBlackTree V
  RBT      : Color -> RedBlackTree V ->-> V -> RedBlackTree V -> RedBlackTree V
{-# COMPILE AGDA2SCALA RedBlackTree #-}

lookup : {V : Set} -> V ->-> RedBlackTree V -> V
lookup defaultVal key EmptyRBT =
  defaultVal
lookup defaultVal key (RBT c left currKey x right) =
  if key <ᵇ currKey then
    lookup defaultVal key left
  else
    if currKey <ᵇ key then
      lookup defaultVal key right
    else
      x
{-# COMPILE AGDA2SCALA lookup #-}

into Scala code:

package examples

object rbt:
  enum Color:
    case Red
    case Black

  enum RedBlackTree[V]:
    case EmptyRBT extends RedBlackTree[Nothing]
    case RBT[V](x0: Color, x1: RedBlackTree[V], x2: Long, x3: V, x4: RedBlackTree[V]) extends RedBlackTree[V]

  def lookup[V](x1: V, x2: Long, x3: RedBlackTree[V]): V = x3 match
    case RedBlackTree.EmptyRBT =>
      x1
    case RedBlackTree.RBT(p0, p1, p2, p3, p4) =>
      if x2 < p2 then
        lookup(x1, x2, p1)
      else if p2 < x2 then
        lookup(x1, x2, p4)
      else
        p3

More Agda examples and output Scala 3 code.

Architecture

We use 2 internal representation

  1. AgdaIR is close to Agda compiler representation
  2. ScalaIR is close to Scala language representation
             TCM glue                      prettyPrint
Definition ==========> AgdaIR ==> ScalaIR ============> String

At the end we choose between Scala 2 and Scala 3 syntax. Possible future directions: Kotlin, Java, JVM bytecode

Identifier mapping

Agda constructor names are not always valid Scala identifiers (e.g. []). We apply a small mapping table for common constructors and then sanitize the result:

  • []Nil
  • _∷_ / _::_ / _cons_Cons
  • _,_Pair

See Agda.Compiler.Scala.Name.NamePolicy.

Working with source code

  • continuous compilation loop using entr
find -name '*.hs' | entr cabal test all
find . -name '*.hs' | entr cabal test agda2scala-test
find . -name '*.hs' | entr cabal test agda2scala-props

or using ghcid

ghcid
  • Build
cabal build all
  • Run tests
cabal test all
cabal test agda2scala-test
cabal test agda2scala-props
  • Simple way to run Scala backend
cabal run -- agda2scala --help
cabal run -- agda2scala ./examples/adts.agda
  • Generate Scala2 output
cabal run -- agda2scala --compile --no-main --scala-dialect=Scala2 --out-dir=scala2/src/main/scala ./examples/adts.agda
  • Generate Scala3 (dotty) output
cabal run -- agda2scala --compile --no-main --scala-dialect=Scala3 --out-dir=scala3/src/main/scala ./examples/adts.agda
cabal run -- agda2scala --help
cabal run -- agda2scala ./examples/adts.agda
cabal run -- agda2scala --compile --no-main --scala-dialect=Scala2 --out-dir=scala2/src/main/scala ./examples/adts.agda
  • format code
fourmolu -i $(find src app test -name '*.hs')
cabal-fmt -i agda2scala.cabal
  • static code analysis using HLint:
hlint src app test

end-to-end tests

There are Scala 3 and Scala 2 projects for code generated from Agda examples

They have unit tests, that use code generated from examples.

                agda2scala                
                (generate)                     sbt test
Agda examples ==============> src/main/scala <============ src/test/scala

checks:
* compile Agda examples to Scala code
* run Scala unit tests that calls that Scala code

Those tests are run on CI - Github Actions.

Generate Scala 2 code from Agda examples and running tests:

cabal run -- agda2scala --compile --no-main --scala-dialect=Scala2 --out-dir=scala2/src/main/scala ./examples/adts.agda
cabal run -- agda2scala --compile --no-main --scala-dialect=Scala2 --out-dir=scala2/src/main/scala ./examples/rbt.agda
cd scala2
sbt ~test

generate Scala 3 code:

cabal run -- agda2scala --compile --no-main --scala-dialect=Scala3 --out-dir=scala3/src/main/scala ./examples/adts.agda
cabal run -- agda2scala --compile --no-main --scala-dialect=Scala3 --out-dir=scala3/src/main/scala ./examples/rbt.agda
cd scala3
sbt ~test

Resources

About

Scala 2 and Scala 3 backend for Agda

Topics

Resources

License

Stars

0 stars

Watchers

2 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors