Paradigmi di Programmazione e Sviluppo — Prof. Viroli

Java/Scala/Prolog Integration and Cross-Platform Scala

Integration: Java/Scala/Prolog and Cross-Platform Scalaa.a. 2025/2026

In this lesson

1. Why Java-Scala-Prolog Integration

Each language in the trio brings distinct strengths:

LanguageStrengthsRole in integration
PrologDeclarative style, concise algorithms, intrinsic search and backtracking, rapid prototyping, meta-programming facilities, natural for symbolic AICore logic, reasoning, search algorithms
JavaImperative + OOP, massive library ecosystem (graphics, networking, file I/O, XML), efficient, widely understoodInfrastructure, I/O, GUI, libraries
ScalaFull Java interop, full FP support, for-comprehension for search, modern OOP + FP integrationGlue layer, FP encapsulation of Prolog
The proposed scenario

Use Java/Scala for aspects "in the large" and interactions with the operating system. Use Prolog for core data and behaviour that benefits from search and declarative specification — like modelling an agent's mind, a game's computer strategy, or storing and querying knowledge.

Example application areas: an agent whose reasoning engine is written in Prolog; a game where the AI opponent's strategy is specified declaratively in Prolog; using Prolog databases for storing knowledge and running inference rules.

2. Integration Approaches

The course identifies four integration strategies, each with different trade-offs:

ApproachDescriptionExample
Library-orientedA library for language A allows exploiting language B. Handy but the glue code can be complicated.tuProlog's Term hierarchy, Prolog engine API
Language-orientedLanguage A is extended with abstractions from B. Often too complicated.Hypothetical "Java+Prolog" combined syntax
Encapsulation-orientedCode in B is encapsulated into A's abstractions. Nice trade-off.Java class becomes Prolog library; Scala wraps Prolog theory
DSL-embedding (modern)B's features are provided as an internal DSL in A (not used in tuProlog).Scala DSL for logic programming

In tuProlog 4.0, the following technologies are available for each direction:

3. Prolog Calling Java

tuProlog allows Prolog code to create and manipulate Java objects directly through three key predicates.

new_object/3

Creates a Java object from Prolog:

new_object(+ClassName, +ArgumentList, -ObjRef)

Calls the Java constructor of ClassName, passing ArgumentList, and unifying ObjRef with the resulting object reference (an opaque term like '$obj2').

<-/2 (Method Call)

Calls a method on a Java object (no return value expected):

ObjRef <- MethName(Args)

returns/2 (Method Call with Result)

Calls a method and captures the return value:

ObjRef <- MethName(Args) returns ObjRef2

Example: Creating a Window from Prolog

Vector Example

A library-like Prolog module wrapping java.util.Vector:

vector_new(V) :- new_object('java.util.Vector', [], V).
vector_add(V, E) :-
    new_object('java.lang.Integer', [E], I),
    V <- add(I).
vector_size(V, S) :- V <- size returns S.
vector_lookup(V, P, E) :- V <- elementAt(P) returns E.

% Usage:
% ?- vector_new(X), vector_add(X,1), vector_add(X,2), vector_lookup(X, 0, N).
Editor's note

The Java interoperability predicates effectively turn Prolog into a scripting language for the JVM, giving it access to the entire Java standard library and any other JVM-hosted library.

4. Java Calling Prolog

The tuProlog Java library provides a full API for embedding a Prolog engine in Java applications. The main classes are in the alice.tuprolog package.

Core Classes

ClassPurpose
PrologThe Prolog virtual machine (engine)
TheoryA Prolog theory (database), as text or list of clauses
SolveInfoThe outcome of solving a goal
TermThe root of the term hierarchy: Var, Number (Int, Double, etc.), Struct

Direct Goal Solution

import alice.tuprolog.*;

public class Test1 {
    public static void main(String[] args) throws Exception {
        Prolog engine = new Prolog();
        SolveInfo info = engine.solve("append([1],[2,3],X).");
        System.out.println(info.getSolution());
        // "append([1],[2,3],[1,2,3])"
    }
}

Getting All Solutions

public class Test2 {
    public static void main(String[] args) throws Exception {
        Prolog engine = new Prolog();
        SolveInfo info = engine.solve("append(X,Y,[1,2,3]).");
        while (info.isSuccess()) {
            System.out.println("solution: " + info.getSolution()
                + " - bindings: X/" + info.getTerm("X")
                + " Y/" + info.getTerm("Y"));
            if (engine.hasOpenAlternatives()) {
                info = engine.solveNext();
            } else { break; }
        }
    }
}

Creating and Using Terms Programmatically

public class Test3 {
    public static void main(String[] args) throws Exception {
        Prolog engine = new Prolog();

        // Parse a term from string
        Term list1 = engine.toTerm("[1]");

        // Build terms programmatically
        Term list2 = new Struct(new Term[]{new Int(2), new Int(3)});

        // Build compound term
        Term app = new Struct("append", list1, list2, new Var("X"));

        SolveInfo info = engine.solve(app);
        System.out.println(info.getSolution());
        // "append([1],[2,3],[1,2,3])"
    }
}

Term Hierarchy

Reading Results

public class Test4 {
    public static void main(String[] args) throws Exception {
        Prolog engine = new Prolog();
        Term t = new Struct();
        for (int i = 10; i >= 0; i--) {
            t = new Struct(new Int(i), t);
        }
        Term app = new Struct("append", t, t, new Var("X"));
        SolveInfo info = engine.solve(app);
        Struct t2 = (Struct) info.getTerm("X");
        for (java.util.Iterator i = t2.listIterator(); i.hasNext();) {
            System.out.print(" " + i.next());
        }
        // 0 1 2 ... 10 0 1 2 ... 10
    }
}

Setting a Theory

public class Test5 {
    public static void main(String[] args) throws Exception {
        Prolog engine = new Prolog();
        Theory t = new Theory(
            "search(E,[E|_]). " +
            "search(E,[_|L]) :- search(E,L)."
        );
        engine.setTheory(t);
        SolveInfo info = engine.solve("search(1,[1,2,3]).");
        System.out.println(" " + info.getSolution());
    }
}
The examiner will ask

You should be able to write Java code that creates a Prolog engine, sets a theory, solves a goal, and iterates through multiple solutions. Be prepared to explain the Term hierarchy and how to build and inspect terms programmatically.

5. Prolog Libraries Written in Java

This is the encapsulation-oriented approach for Prolog using Java: a Java class that implements a Prolog library, where each Java method defines a Prolog predicate.

How It Works

Example Library

public class TestLibrary extends Library {
    private int termAsInt(Term t) {
        return ((Number) t).intValue();
    }

    // try with: X is sum(10, 20).
    public Term sum_2(Term arg0, Term arg1) {
        return new Int(termAsInt(arg0) + termAsInt(arg1));
    }

    // try with: X is minus(20, 10).
    public Term minus_2(Term arg0, Term arg1) {
        return new Int(termAsInt(arg0) - termAsInt(arg1));
    }

    // try with: sum(Y, 8, 21).
    public boolean sum_3(Term arg0, Term arg1, Term arg) {
        try {
            if (arg instanceof Var)
                return arg.unify(this.getEngine(), sum_2(arg0, arg1));
            if (arg0 instanceof Var)
                return arg0.unify(this.getEngine(), minus_2(arg, arg1));
            return arg1.unify(this.getEngine(), minus_2(arg, arg0));
        } catch (Exception e) { return false; }
    }
}
Multi-mode predicates

The sum_3 method demonstrates a predicate that works in multiple modes: if the result is a variable, it computes the sum; if one input is a variable, it computes the difference. This is the Java side implementing Prolog-style full relationality.

6. Scala Using Prolog

The most elegant integration uses Scala's functional abstractions to wrap Prolog theories, treating solutions as a lazy stream. This is the encapsulation-oriented approach applied to Scala.

Key Components

Scala provides implicit conversions and an engine factory:

Using the Integration

object TryScala2P extends App:
  import Scala2P.{*, given}

  val engine: Term => LazyList[Term] = mkPrologEngine("""
    member([H|T], H, T).
    member([H|T], E, [H|T2]) :- member(T, E, T2).
    permutation([], []).
    permutation(L, [H|TP]) :- member(L, H, T), permutation(T, TP).
  """)

  // Solutions as a lazy stream
  engine("permutation([1,2,3], L)") foreach (println(_))
  // permutation([1,2,3],[1,2,3]) ... permutation([1,2,3],[3,2,1])

  // Using structured terms
  val input = Struct("permutation", (1 to 20), Var())
  engine(input) map (extractTerm(_, 1)) foreach (println(_))
Monadic view of Prolog

The LazyList wrapping makes Prolog's multiple solutions behave like a monadic stream — you can map, filter, and for-comprehend over them, bridging the LP and FP paradigms.

Functional/Monadic Approach to Logic Programming

The course also shows a pure-FP encoding of Prolog-like search in Scala, demonstrating how the same patterns can be expressed without a Prolog engine:

def lookup[A](list: List[A]): LazyList[(A, Int, List[A])] = list match
  case Nil => LazyList()
  case h :: t =>
    (h, 0, t) #::
    (for (e, n, t2) <- lookup(t) yield (e, n + 1, h :: t2))

println(lookup(List(10, 20, 30, 20)).toList)
// List((10,0,List(20,30,20)), (20,1,List(10,30,20)), ...)

// Find positions of 20
println(lookup(List(10, 20, 30, 20))
  .collect { case (20, n, _) => n }.toList)
// List(1, 3)

7. Scala Cross-Platform: Scala.js and Scala Native

Scala is not just a JVM language. It compiles to JavaScript (Scala.js) and native machine code (Scala Native), enabling write once, run on three platforms.

The Three Target Ecosystem

TargetOutputUse caseMaturity
JVMBytecodeBackend services, enterprise, AndroidProduction-ready
Scala.jsJavaScript (or WebAssembly)Web frontends, Node.js, browser librariesStable since 2014
Scala NativeNative executable (LLVM)CLI tools, embedded, cloud functionsMaturing

SBT Configuration

// project/plugins.sbt
addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.21.0")
addSbtPlugin("org.scala-native" % "sbt-scala-native" % "0.5.10")

// build.sbt
import sbt.Keys.scalaVersion
ThisBuild / scalaVersion := "3.3.5"
name := "scala-js-native"

val js = project.settings(
  scalaJSUseMainModuleInitializer := true
).enablePlugins(ScalaJSPlugin)

val native = project.settings(
  nativeConfig ~= { _.withBaseName("native-out") }
).enablePlugins(ScalaNativePlugin)

Cross-Project Configuration

// Using sbt-crossproject for multi-platform builds
lazy val shared = crossProject(JSPlatform, JVMPlatform, NativePlatform)
  .crossType(CrossType.Full)       // or CrossType.Pure
  .settings(libraryDependencies ++= Seq(
    "org.scalatest" %%% "scalatest" % "3.2.19"
  ))
  .jsSettings(/* JS-specific */)
  .jvmSettings(/* JVM-specific */)
  .nativeSettings(/* Native-specific */)

How Scala.js and Scala Native Work

The compiler takes plain Scala code and, using a compiler plugin, produces an intermediate representation (IR). The IR is then optimised, linked, and compiled to the target platform. Scala 3 has native, out-of-the-box support for both JavaScript and Native targets with advanced features like givens, extension methods, and enums compiling seamlessly.

Architecture for Multi-Platform: Clean Architecture

The recommended pattern is Ports & Adapters (Hexagonal Architecture):

Key benefits

Shared codebase eliminates duplication and error propagation. Full-stack orientation lets you write both backend and frontend in the same language. Access to platform-specific libraries through facades.

8. Facades, ScalablyTyped, and Ecosystem

To bridge Scala with platform-native APIs (JavaScript in the browser, C in the OS), Scala.js and Scala Native use a mechanism called facades — type declarations that tell the compiler how to emit correct calls to foreign APIs.

Scala.js Facades

import scala.scalajs.js
import js.annotation._

// Facade for browser's window.alert
@js.native
@JSGlobal
object window extends js.Object {
  def alert(msg: String): Unit = js.native
}

// Using a facade
window.alert("Hello from Scala.js!")

Scala Native C Bindings

import scalanative.unsafe._
import scalanative.unsigned._

@link("m")
@extern
object libm {
  def sqrt(d: CDouble): CDouble = extern
}

// Using a C binding
val result = libm.sqrt(16.0)  // 4.0

ScalablyTyped: Automated Facade Generation

Writing facades manually for large libraries is impractical. ScalablyTyped solves this by automatically converting TypeScript definition files (.d.ts) into type-safe Scala.js facades:

// With ScalablyTyped, NPM packages feel like native Scala libraries
import scala.scalajs.js
import customtypings.tensorflowTfjs.mod as tf

val data = js.Array(1.0, 2.0, 3.0, 4.0)
val tensor = tf.tensor1d(data)
val squared = tensor.square()
tensor.dispose()

SBT Configuration for ScalablyTyped

lazy val scalablytyped = (project in file("scalablytyped"))
  .enablePlugins(ScalaJSBundlerPlugin, ScalablyTypedConverterGenSourcePlugin)
  .settings(name := "scala-js-facade-scalablytyped",
    scalaJSUseMainModuleInitializer := true,
    Compile / npmDependencies += "@tensorflow/tfjs" -> "4.22.0",
    stOutputPackage := "customtypings")

Modern Frontend Frameworks for Scala.js

FrameworkArchitectureKey feature
LaminarReactive, Observable-basedNo external JS deps, targets DOM directly
TyrianElm Architecture (Model-View-Update)Integrates with Cats Effect

Triple-Target Ecosystem

Major Scala libraries are published for all three targets using %%% (instead of %%):

CategoryLibraries
Functional ProgrammingCats, Cats Effect, FS2
HTTP / APIsHttp4s, Tapir, Smithy4s
SerializationCirce, uPickle
TestingMUnit, Weaver
Caveats

Pure cross-platform projects cannot use general JVM ecosystem features (threads, filesystem) unless a cross-platform facade exists. Scala Native toolchain is still maturing: smaller library support, longer compile times (LLVM linking), and incomplete standard library coverage. Bundle sizes for Scala.js can reach ~1MB before tree-shaking.

Check Your Understanding

What are the four integration approaches discussed in the course? Which ones are used in tuProlog?

Library-oriented, language-oriented, encapsulation-oriented, and DSL-embedding. tuProlog uses: library-oriented (Prolog calling Java via new_object/3, <-/2, returns/2; Java calling Prolog via Term/Engine API), and encapsulation-oriented (Java class as Prolog library; Scala wrapping Prolog theory).

How do you create a Java object from Prolog and call a method on it? Show the predicates.

new_object(+ClassName, +Args, -Ref) creates the object. Ref <- methodName(Args) calls a void method. Ref <- methodName(Args) returns Result captures a return value. Example: new_object('javax.swing.JFrame',[],F), F <- setSize(400,300), F <- setVisible(true).

Explain the tuProlog Term hierarchy. How do you build a term programmatically in Java?

Term is the root. Var for variables, Number (Int, Double, Long, Float) for numeric values, and Struct for compound terms (functors, lists). You build terms by constructing instances: new Struct("append", list1, list2, new Var("X")) creates append/3, or use engine.toTerm("[1]") to parse from string.

How do you iterate through all solutions of a Prolog goal from Java?

Call engine.solve(goal) to get the first SolveInfo. While info.isSuccess(), process it. Call engine.hasOpenAlternatives() to check if more solutions exist, then engine.solveNext() to get the next one. Break when no more alternatives.

Describe the Scala integration with Prolog using mkPrologEngine. What does the LazyList provide?

mkPrologEngine takes a Prolog theory (as a string) and returns a function Term => LazyList[Term]. The LazyList wraps Prolog's multiple solutions as a lazy, potentially infinite stream. It integrates Prolog's search with Scala's functional abstractions, allowing map, filter, for-comprehension, and other collection operations over the results.

What are the three target platforms for Scala, and how do you configure an SBT multi-project for all three?

JVM, JavaScript (Scala.js), and Native (Scala Native). Configure via sbt-scalajs and sbt-scala-native plugins. Use sbt-crossproject with crossProject(JSPlatform, JVMPlatform, NativePlatform).crossType(CrossType.Full) to define a shared module and platform-specific modules.

What is a Scala.js facade, and how does ScalablyTyped automate facade generation?

A facade is a type declaration (using @js.native, @JSGlobal, etc.) that tells the Scala.js compiler how to emit correct JavaScript calls without generating implementation bytecode. ScalablyTyped automatically parses TypeScript .d.ts definition files and generates type-safe Scala facades, translating TS union types to Scala 3 union types, and mapping NPM packages to Scala packages.

Explain the Ports & Adapters (Clean Architecture) pattern for cross-platform Scala projects.

The pure core (domain models, business logic) lives in a shared module using only cross-platform libraries. Platform-specific implementations (adapters) are in separate modules for each target: JDBC/Doobie for JVM, Laminar/Tyrian for JS, POSIX C interop for Native. Generic traits in the shared module define the interfaces; SBT handles cross-linking automatically.