Discovering OCaml as a Scala Developer Part 1: The Journey Begins

As a Scala developer, I will document my journey of learning OCaml and how it compares to Scala

  • OCaml
  • Scala
  • Functional Programming
  • Tooling

Introduction

Thanks to @xvw for introducing me to OCaml. Since this blog uses an OCaml static site generator named YOCaml, I thought learning more about OCaml would be a good idea, and a good way to share my experience with other people who want to learn it too.

In this first part, I will talk briefly about how to build a project in both languages. But first, let's talk about the two languages and their ecosystems, mostly paraphrasing their respective websites and Wikipedia pages.

Scala

Scala is a general-purpose programming language that supports both object-oriented and functional programming paradigms. At its inception, it was designed to be a better Java, but it has since evolved into a language used for a wide range of applications. Its main target is the JVM, with Scala being de facto compatible with Java and its libraries, but Scala can also target JavaScript via Scala.js and native code via LLVM with Scala Native. Scala is known for its powerful type system; its creators took a lot of inspiration from Haskell, and also from other languages like OCaml. We are currently at Scala 3, a major release that brings a lot of new features and improvements to the language, including an optional new syntax that is more concise and easier to read.

OCaml

OCaml is a general-purpose programming language that supports functional, imperative, and object-oriented programming paradigms. It is a statically typed language with type inference, which means that the compiler can usually deduce the types of expressions without requiring explicit type annotations. OCaml's inference is principal, meaning that when a type can be inferred, the compiler picks the most general one: for example, let f x = x is inferred as 'a -> 'a, and let f g x = g x as ('a -> 'b) -> 'a -> 'b. OCaml is known for its powerful type system, which includes features such as algebraic data types, pattern matching, and higher-order functions. OCaml is also known for its performance, as it compiles to native code and has a garbage collector that can be tuned for low-latency applications. OCaml has a strong ecosystem of libraries and tools, including the OPAM package manager and the Dune build system.

Build system and package manager

Both Scala and OCaml have their own build systems and package managers. On the Scala side, several build tools coexist: sbt, mill, gradle, maven, and also scala-cli, which has been integrated into the language since a minor version of Scala 3. In OCaml, the package manager and the build system have traditionally been separated: OPAM handles dependencies, while Dune takes care of building (though Dune now also offers its own package-management features). OPAM also lets you easily manage your OCaml versions alongside your dependencies, whereas in a Scala project you typically need a separate tool like SDKMAN to manage your Java version, with dependencies handled by the build system, which most of the time delegates to coursier under the hood.

     SCALA                              OCAML
     ─────                              ─────

┌──────────┐                      ┌──────────┐
│  SDKMAN  │ (JVM versions)       │          │
└──────────┘                      │          │
┌──────────┐                      │   OPAM   │ (everything:
│ coursier │ (deps resolution)    │          │  versions +
└──────────┘                      │          │  deps +
┌──────────┐                      │          │  installation)
│   sbt    │ (build)              └──────────┘
└──────────┘                      ┌──────────┐
                                  │   Dune   │ (build)
                                  └──────────┘

3 tools, overlapping roles        2 tools, traditionally separated
sbt orchestrates everything       OPAM and Dune cooperate
                                  via generated .opam files
                                  (Dune now also offers some
                                  package-management features)

Now creating a simple hello world with both languages

In both languages, you can create a project with a single command. For Scala, you can use the Scala seed project with sbt like this:

sbt new scala/scala3.g8
A template to demonstrate a minimal Scala 3 application

name [Scala 3 Project Template]: scala-play

You will get a simple project generated by sbt, with an sbt file that looks like this:

val scala3Version = "3.8.3"

lazy val root = project
  .in(file("."))
  .settings(
    name := "scala-play",
    version := "0.1.0-SNAPSHOT",

    scalaVersion := scala3Version,

    libraryDependencies += "org.scalameta" %% "munit" % "1.3.0" % Test
  )

and a simple main at src/main/scala/Main.scala that looks like this:

@main def hello(): Unit =
  println("Hello world!")
  println(msg)

def msg = "I was compiled by Scala 3. :)"

To run it, simply run sbt run and you will see the output of the program.

For OCaml, you can use Dune to create a simple project. If you want, you can first use OPAM to pin the OCaml version you want to use:

opam switch create ocaml-5.4.0 5.4.0 # or if you already have it installed
opam switch ocaml-5.4.0 # switch to the version you want to use
eval $(opam env) # will set the environment variables for the version you just switched to
opam install dune # install dune if you don't have it already for the ocaml version you just switched to

Then you can create a simple Dune project like this:

dune init proj ocaml-play

You will get a simple project generated by Dune, with a dune-project file that looks like this:

(lang dune 3.22)

(name ocaml-play)

(generate_opam_files true)

(source
 (github username/reponame))

(authors "Author Name <author@example.com>")

(maintainers "Maintainer Name <maintainer@example.com>")

(license LICENSE)

(documentation https://url/to/documentation)

(package
 (name ocaml-play)
 (synopsis "A short synopsis")
 (description "A longer description")
 (depends ocaml)
 (tags
  ("add topics" "to describe" your project)))

; See the complete stanza docs at https://dune.readthedocs.io/en/stable/reference/dune-project/index.html

and a simple main at bin/main.ml that looks like this:

let () = print_endline "Hello, World!"

To run it, simply run dune exec ./bin/main.exe and you will see the output of the program.

Conclusion

As a Scala developer, I found OCaml's tooling refreshingly straightforward and just as easy to use as Scala's. The split between OPAM and Dune is not really a problem in practice: Dune manages the .opam file for you, and OPAM's role for version management is similar to SDKMAN in the JVM world or nvm in the Node.js world. I came away pleasantly surprised by how modern the OCaml tooling feels, and I'm looking forward to learning more about the language and its ecosystem.

That wraps up this first part. The next part will be about creating a simple CRUD HTTP API with some in-memory data.

Acknowledgements

A big thank you to @xvw for reviewing this article and sharing valuable feedback and OCaml insights.