7

I'm using SML/NJ, and I need to use a set of functions that are in a certain file f1.sml inside another file f2.sml.

However, I'm not running f2.sml directly, rather, I'm importing it from somewhere else.

If I use the use command in f2.sml with the path to f1.sml relative to f2.sml perspective, by the time I import f2.sml, it will look for the supplied path from the running script perspective.

I cannot use absolute paths, and I'd like not to merge the two files contents.

Sorry if this is a trivial application of the language, but I'm new to SML, and couldn't find the answer yet.

leolovesai
  • 73
  • 3

1 Answers1

12

I recommend using SML/NJ's Compilation Manager (CM). It's a build system for SML code in the context of SML/NJ. It can get quite complicated if you need more advanced features, but it's easy to get started. I'll show you a barebones structure and you can adjust as needed. It already comes installed with SML/NJ, so there's no installation process.

I'll use the following directory structure for this example (the file extensions are not imposed, just a convention):

.
├── build.cm
└── src
    ├── foo.fun
    ├── foo.sig
    └── main.sml

build.cm

group
  (* CM allows you to selectively export defined modules (structures,
     signatures and functors) by listing them here. It's useful for
     libraries. *)

  source (-)       (* export all defined modules *)

  structure Main   (* OR, export selectively *)
  signature FOO
  functor Foo
is
  (* Import the SML standard library, aka Basis.  *)
  (* See: http://sml-family.org/Basis/ *)
  $/basis.cm

  (* Import the SML/NJ library *)
  (* Provides extra data structures and algorithms. *)
  (* See: https://www.smlnj.org/doc/smlnj-lib/Manual/toc.html *)
  $/smlnj-lib.cm

  (* List each source file you want to be considered for compilation. *)
  src/main.sml
  src/foo.sig
  src/foo.fun

src/main.sml

structure Main =
  struct
    (* You don't have to import the `Foo` functor. *)
    (* It's been done in build.cm already. *)
    structure F = Foo()

    fun main () =
      print (F.message ^ "\n")
  end

src/foo.sig

signature FOO =
  sig
    val message : string
  end

src/foo.fun

(* You don't have to import the `FOO` signature. *)
(* It's been done in build.cm already. *)
functor Foo() : FOO =
  struct
    val message = "Hello, World!"
  end

Usage

Having the structure in place, you can start compiling using CM.make and running by calling whatever functions you've defined:

$ sml
Standard ML of New Jersey v110.82 [built: Tue Jan  9 20:54:02 2018]
- CM.make "build.cm";
val it = true : bool
-
- Main.main ();
Hello, World!
val it = () : unit
Ionuț G. Stan
  • 176,118
  • 18
  • 189
  • 202
  • 1
    Excellent answer. – sshine May 30 '18 at 08:02
  • 1
    Thanks, @SimonShine. – Ionuț G. Stan May 30 '18 at 10:14
  • 1
    This is all great and so on, but it does not really answer the question of how one would import another file relative to the one, which is being edited. Could you add that information? Perhaps in the comment, where you say, that import is not needed, because it is already in `build.cm`? (For example: What do I do, if one module depends on a second module? Somehow it needs to be referenced in the first module. (I guess?)) – Zelphir Kaltstahl Dec 05 '21 at 13:28
  • 1
    @ZelphirKaltstahl you can reference any module from any module (bar circular dependencies) as long as you list all of them in the CM file. Listing them inside that file basically flattens the module hierarchy. You can use any file system path inside the CM file, but inside your SML files all you have to do is just use the module names. Makes sense? – Ionuț G. Stan Dec 07 '21 at 06:48