evaluation monitor in ocaml - ocaml

What I am trying to achieve is similar to a logging facility but for monitoring and streaming arbitrary data from a running simulation. Here is the simplified situation:
module Sim (V:VEC) = struct
module V = V
module M = struct type data = V.t end
let loop n init_data =
let running_data = ref init_data in
for _i = 1 to n do
(*?*) (* monitor here: data => outside world *)
rdata := process_data !rdata
done
end
While simulation loops, at the ? I may want to 'tap' data and accumulate it. Other times, I want to just let it run and disable the data stream with minimal overhead -- the ? is in a tight loop. So I want the streaming to be configurable with little cost.
What I have now is this:
module Sim (V:VEC) = struct
module V = V
module M = struct type data = V.t end
let data_monitor : (M.data -> unit) ref = ref (fun d -> ())
let loop n init_data =
let running_data = ref init_data in
for _i = 1 to n do
!data_monitor !rdata; (* monitor here *)
rdata := process_data !rdata
done
end
Ie. I put a stub monitoring function reference in there. In the actual application script I can then assign a function which e.g. accumulates the data values into a list or some such. It works.
So the question is: is this the best/lowest overhead/nicest way to achieve what I want?
This approach seems a bit hackish, I would rather use the module system instead of function pointers. However, the data type to be streamed is only defined inside the functor Sim. So making a monitoring function in another module Sampler outside of Sim and parametrizing Sim by that, seems not convenient and/or requires duplication of code or recursive modules. I tried, but I was not able to make all types equal.
Edit: Here is roughly what it tried without function refs:
module Sampler (V:VEC) : sig
module V : VEC
type data = V.t
val monitor_data : data -> unit
end
with type data = V.t = struct
module V = V
type data = V.t
let monitor_data data = store_away_the data
end
module Sim (V:VEC) (Sampler:??) : sig
...
end with type M.data = V.t
At the ?? I was not sure how to specify the output signature of Sampler, since the input signature VEC is still free; also I was not sure how exactly to make the type equality work. Maybe I'm doing it wrong here.

As discussed in the comments, you may be able to do something like this using higher-order functions (instead of having to resort to a higher-order functor):
module type VEC = sig type t end
module Vec = struct type t = unit end
module Sim (V : VEC) =
struct
module M = struct type data = V.t list end
let process x = x
let rec loop ?(monitor : M.data -> unit = ignore) n data =
if n <= 0 then data
else
(monitor [];
process data |> loop ~monitor (n - 1))
end
module MySim = Sim (Vec)
let monitor _ = print_endline "foo"
let () =
MySim.loop ~monitor 5 ()
loop above takes an optional function as argument, which you can pass with the syntax ~monitor:my_fun or ~monitor:(fun data -> ...). If you already have a value called monitor in scope, you can simply do ~monitor to pass it. If you don't pass anything, the default value is ignore (i.e. fun _ -> ()).
I also rewrote loop in recursive style. The code above prints foo 5 times. Note that your monitor function can still come from Sampler module, you just have no need to pass the whole module in when instantiating Sim.
EDIT: If you still want to declare a higher-order functor, here is how you do it (...)
EDIT 2: Changed the example given additional information that the reason for the higher-order functor is that there are multiple monitoring functions to call. Note that in this case, there are still other solutions besides a higher-order functor: you could group the functions into a record, and pass the record to loop. Similar to this, you could pass a first-class module. Or, you could create one function that takes a variant type whose cases indicate at what stage the monitoring function is being called, and carry the data associated with each stage. You can also use classes for this, though I wouldn't recommend it. The functor approach does have an advantage, however, if you are committed to declaring M inside Sim.
I have omitted the signature VEC from the sketch because I'm under the impression that the questioner understands where to add it, and there is no problem with it :)
module type SAMPLER =
sig
type data
val monitor : data -> unit
val monitor' : data list -> unit
end
(* These are created inside Sim. *)
module type DATA =
sig
type data
val show : data -> string
end
(* Note that I am using destructive substitution (:=) to avoid the need
to have a type data declared in the body of MySampler below. If you
use a regular type equality constraint, you need to add a field
"type data = Data.data" to the body. *)
module type SAMPLER_FN =
functor (Data : DATA) -> SAMPLER with type data := Data.data
(* This is the higher-order functor (it takes another functor as an
argument). *)
module Sim (Sampler_fn : SAMPLER_FN) =
struct
(* Corresponds to module "Sim.M" in the question. *)
module Data =
struct
type data = string
let show s = s
end
(* Note that without additional type constraints or rearrangements,
the type data is abstract to Sampler (more precisely, Sampler_fn
is parametric over Data). This means that Sampler_fn can't
analyze values of type data, which is why we need to provide
functions such as Data.show to Sampler_fn for instances of
Sampler_fn to be "useful". If you are trying to avoid this and
are having trouble with these specific constraints, let me
know. The ability to pass types and related values (functions
in this case) to Sampler_fn is the main argument in favor of
using a higher-order functor. *)
module Sampler = Sampler_fn (Data)
let simulate x =
(* Call one monitoring function. *)
Sampler.monitor "hi!";
(* Do some computation and call another monitoring function. *)
Sampler.monitor' ["hello"; "world"]
end
Usage:
module MySampler (Data : DATA) =
struct
let monitor data = data |> Data.show |> print_endline
let monitor' data =
data
|> List.map Data.show
|> String.concat " "
|> print_endline
end
module MySim = Sim (MySampler)
let () = MySim.simulate ()
This prints
hi!
hello world

For completeness:
Building on the functor part of antron's answer, this is what I am currently using. It is still a bit involved, and maybe it could be made more concise, but it has some nice advantages. Namely: the monitoring of individual aspects can be switched on and off in a centralized place (a module of type SAMPLER) and arbitrary types can be exported, even if they become defined only somewhere inside the simulator module.
I define the monitoring (=sampling) modules and module types like so:
module type STYPE = sig type t end
module type SSAMPLER = sig
type t
val ev : t React.event
val mon : t -> unit
end
module type SAMPLER_FN = functor (Data : STYPE) -> SSAMPLER
with type t := Data.t
(* stub sampler function for a single one *)
module Never : SAMPLER_FN = functor (Data : STYPE) -> struct
let ev = React.E.never
let mon = ignore
end
(* event primitive generating sampling function *)
module Event : SAMPLER_FN = functor (Data : STYPE) -> struct
let (ev : Data.t React.event), mon' = React.E.create ()
let mon = mon' ?step:None
end
Here, I am using the React library to generate output streams of data. The React.E.never event does nothing and corresponds to sampling being switched off. Then the full sampling configuration is specified like so:
(* the full sampling config *)
module type SAMPLER = sig
val sampler_pos : (module SAMPLER_FN)
val sampler_step : (module SAMPLER_FN)
(* and several more... *)
end
module NoSampling : SAMPLER = struct
let sampler_pos = (module Never: SAMPLER_FN)
let sampler_step = (module Never: SAMPLER_FN)
(* ... *)
end
(* default sampling config *)
module DefaultSampling : SAMPLER = struct
include NoSampling
(* this is only possible when using first class modules *)
let sampler_pos = (module Event : SAMPLER_FN)
end
One could avoid the first-class modules, but then the convenient inclusion and override in DefaultSampling would not be allowed.
In the simulation library code this is used like this:
module type VEC = sig
type t
val zeropos : t
val wiggle : t -> t
end
module Sim (V:VEC) (Sampler:SAMPLER) = struct
module V = V
module M = struct
type t = { mutable pos : V.t }
val create () = { pos=V.zeropos }
module Sampler_pos = (val Sampler.sampler_pos) (struct type nonrec t = t end)
let update f m = m.pos <- f m.pos
end
module Sampler_b = (val Sampler.sampler_b) (struct type t = int end)
let loop n (running_data:M.t) =
for i = 1 to n do
(* monitor step number: *)
Sampler_b.mon i;
(* monitor current pos: *)
Sampler_pos.mon running_data;
M.update V.wiggle running_data
done
end
Here, the sampling functors are applied at appropriate places in the simulation loop. (val ...) is again necessary only because of the first class module wrapping.
Finally, in an application script, one would then do this:
module Simulator = Sim (V) (DefaultSampling);;
let trace = Simulator.M.Sampler_pos.ev
|> React.E.fold (fun l h -> h :: l) []
|> React.S.hold [];;
let init_m = Simulator.M.create () in
Simulator.loop 100 init_m;;
React.S.value trace;;
The last line then contains the accumulated list of values of type Simulator.M.t that occurred during the loop. Monitoring of the step counter (a silly example) is switched off. By making another sampling functor of type SAMPLER and parametrizing Sim by that, one could further customize the monitoring, if desired.

Related

How to apply [##deriving show] to a type from module parameter of my functor?

I have a functor that takes a Set type like:
module type MySet = functor (S : Set.S) -> sig
val my_method : S.t -> S.elt -> S.elt list option
end
module MySet_Make : MySet = functor (S : Set.S) -> struct
let my_method set el = Some [el] (* whatever *)
end
module IntSet = Set.Make(Int)
module MyIntSet = MySet_Make(IntSet)
S.elt is the type of elements of the set
I want to apply [##deriving show] (from https://github.com/ocaml-ppx/ppx_deriving#plugin-show) to S.elt within my functor somehow, so that in one of my methods I can rely on having a show : S.elt -> string function available.
I feel like it must be possible but I can't work out the right syntax.
Alternatively - if there's a way to specify in the signature that the Set type S was made having elements of a "showable" type.
e.g. I can define:
module type Showable = sig
type t [##deriving show]
end
...but I can't work out how to specify that as a type constraint to elements of (S : Set.S)
You can construct new signatures that specify the exact function show you need:
module MySet_Make(S : sig
include Set.S
val show : elt -> string
end) = struct
let my_method _set el =
print_endline (S.show el);
Some [el]
end
Then you can build the actual module instance by constructing the module with the needed function:
module IntSet = struct
include Set.Make(Int)
(* For other types, this function could be created by just using [##deriving show] *)
let show = string_of_int
end
module MyIntSet = MySet_Make(IntSet)
Ok, after a couple of hours more fumbling around in the dark I found a recipe that does everything I wanted...
First we define a "showable" type, representing a module type that has had [##deriving show] (from https://github.com/ocaml-ppx/ppx_deriving#plugin-show) applied to it:
module type Showable = sig
type t
val pp : Format.formatter -> t -> unit
val show : t -> string
end
(I don't know if there's some way to get this directly from ppx_deriving.show without defining it manually?)
Then we re-define and extend the Set and Set.OrderedType (i.e. element) types to require that the elements are "showable":
module type OrderedShowable = sig
include Set.OrderedType
include Showable with type t := t
end
module ShowableSet = struct
include Set
module type S = sig
include Set.S
end
module Make (Ord : OrderedShowable) = struct
include Set.Make(Ord)
end
end
I think with the original code in my question I had got confused and used some kind of higher-order functor syntax (?) ...I don't know how it seemed to work at all, but at some point I realised my MySet_Make was returning a functor rather than a module. So we'll fix that now and just use a normal functor.
The other thing we can fix is to make MySet a further extension of ShowableSet ... so MySet_Make will take the element type as a parameter instead of another Set type. This makes the eventual code all simpler too:
module type MySet = sig
include ShowableSet.S
val my_method : t -> elt -> elt list option
val show_el : elt -> string
end
module AdjacencySet_Make (El : OrderedShowable) : AdjacencySet
with type elt = El.t
= struct
include ShowableSet.Make(El)
let my_method set el = Some [el] (* whatever *)
let show_el el = El.show el (* we can use the "showable" elements! *)
end
Then we just need an OrderedShowable version of Int as the element type. Int is already ordered so we just have to extend it by deriving "show" and then we can make a concrete MySet:
module Int' = struct
include Int
type t = int [##deriving show]
end
module MyIntSet = MySet_Make(Int')
And we can use it like:
# let myset = MyIntSet.of_list [3; 2; 8];;
# print_endline (MyIntSet.show_el 3);;
"3"

OCaml Typechecking Problem When Using Two Parameterized Modules

I have two modules, Graph and Game, which are parametrized by other modules. They also contain functions f and g which cause typechecking problems when I use them in a testing module. I left out lots of the code that is not important for this problem.
Here is a Graph module that has some module AbstractUSet. AbstractUSet.t is used a lot in the original code. The function f shall later take another function and do some work. The problem is, that the other function comes from another module and has a different type.
module UTYPE = sig
type t
val compare : t -> t -> int
end
module type GRAPH = sig
module U : UTYPE
module AbstractUSet : Set.S
val f : (AbstractUSet.t -> AbstractUSet.t) -> AbstractUSet.t -> AbstractUSet.t
end
module Graph (UA : UTYPE) : (GRAPH with module U=UA) = struct
module U=UA
module AbstractUSet = Set.Make(struct type t=U.t let compare=U.compare end)
let f g uset = g uset
end
The other module is the Game module. It does lots of things with AbstractVSet.t. It contains the function g that shall later be input for the function f from the Graph module.
module type GAME_PIECE = sig
type t
val compare : t -> t -> int
end
module type GAME = sig
module P : GAME_PIECE
module AbstractVSet : Set.S
val g : AbstractVSet.t -> AbstractVSet.t
end
module GameInstance (NA : GAME_PIECE) : (GAME with module P=NA) = struct
module P = NA
module AbstractVSet = Set.Make(struct type t=P.t let compare=P.compare end)
let g vset = vset
end
And this is my module for testing. In the very end, both UTYPE and GAME_PIECE are the same, but I can't make that clear to OCaml. I've commented the lines that don't typecheck. The compiler says there are clashes between MyGame.AbstractVSet.t and MyGraph.AbstractUSet.t.
module TestGame = struct
include(Game(struct type t=string let compare=compare end))
end
module TestGraph = struct
include(Graph(struct type t=string let compare=compare end))
end
module Test = struct
module MyGame = TestGame
module MyGraph = TestGraph
let stringlist = ["a";"b"]
let uset = MyGraph.uset_from_ulist stringlist // works fine
let vset = MyGame.vset_from_vlist stringlist // works fine
let result = MyGraph.f (MyGame.g) vset // does not typecheck!
end
If you ask, why I'm using so many modules: The project is a lot bigger than this code extract, and it is intended to be like this ;)
Can anyone help me how I can make it clear to the OCaml compiler that both UTYPE and GAME_PIECE are the same in the Test module??
Thanks a lot for your help!!!
The first issue is that your abstract set modules are different: the result of two functor applications in OCaml are only equals if the functor applications are applied to exactly the same named modules. For instance, in
module A = struct type t = int end
module F(X:sig type t end) = struct type t end
module FA = F(A)
module B = A
module FA' = F(B)
module C = F(struct type t end)
The types FA.t and Fa'.t are the same
let f (x:FA.t): FA'.t = x
But the types C.t and FA.t are different:
let f (x:C.t): FA'.t = x
Error: This expression has type C.t but an expression was expected of type
FA'.t
But this part can be fixed by not using anonymous structure when they are unnecessary:
module Graph (UA : UTYPE) : (GRAPH with module U=UA) = struct
module U=UA
module AbstractUSet = Set.Make(U)
let f g uset = g uset
end
Then, you are left with the "problem" that Game(M).AbstractUSet and
Graph(M).AbstractUSet defines two distinct types. (Note that this probably the right behavior outside of tests). For the test, one option is to simply expose the information that those modules are the result of functor applications. For instance, one can redefine the GAME module type (and GameInstance functor) to:
module type GAME = sig
type set
module P : GAME_PIECE
module AbstractVSet: Set.S with type t = set
val g : AbstractVSet.t -> AbstractVSet.t
end
module GameInstance (NA : GAME_PIECE) :
(GAME with type set:= Set.Make(NA).t and module P=NA) = struct
module P = NA
module AbstractVSet = Set.Make(NA)
let g vset = vset
end
Here, we have ketp the information that GameInstance(M).AbstractVSet.t is the
same type as Set.Make(M).t.
Combined wit the same operation on the graph part:
module type GRAPH = sig
type set
module U : UTYPE
module AbstractUSet : Set.S with type t = set
val f : (AbstractUSet.t -> AbstractUSet.t) -> AbstractUSet.t -> AbstractUSet.t
end
module Graph (UA : UTYPE) :
(GRAPH with type set := Set.Make(UA).t and module U=UA) = struct
module U=UA
module AbstractUSet = Set.Make(UA)
let f g uset = g uset
end
we are preserving enough type information to keep the equality for the test:
module TestGame = GameInstance(String)
module TestGraph = Graph(String)
module Test = struct
module MyGame = TestGame
module MyGraph = TestGraph
let result vset = MyGraph.f (MyGame.g) vset (* does typecheck *)
end

Functors with multiple inputs in Standard ML

High level question: How do I use functors with multiple arguments in SML?
I've looked at this, this, this and this(PDF). All of them seem to conflict in terms of structure or functor definition syntax, and none of them show anything other than a unary functor.
Specifics: I'm trying to write a web server in Standard ML (you can see the effort here), and have decided to partition it into BUFFER, PARSER and TCPSERVER chunks. The BUFFER and PARSER are both just straightforward structures. The idea with the TCPSERVER is that it handles listening/accepting logic, but allows the user to specify an appropriate buffering/parsing strategy by passing the other two in. What I've got is something like
signature TCPSERVER =
sig
type SockAction
type Request
val serve : int -> (Request -> (INetSock.inet,Socket.active Socket.stream) Socket.sock -> SockAction) -> 'u
end
functor Server (Buf : BUFFER) (Par : PARSER) : TCPSERVER =
struct
type Request = Par.Request
datatype SockAction = CLOSE | LEAVE_OPEN
local
...
[eliding more definitions, including calls to Par.* and Buf.* functions]
...
fun serve port serverFn =
let val s = INetSock.TCP.socket()
in
Socket.Ctl.setREUSEADDR (s, true);
Socket.bind(s, INetSock.any port);
Socket.listen(s, 5);
print "Entering accept loop...\n";
acceptLoop s [] serverFn
end
end
end
The above seems to be accepted by smlnj...
- use "server.sml" ;
[opening server.sml]
type Response =
{body:string, headers:(string * string) list, httpVersion:string,
responseType:string}
val fst = fn : 'a * 'b -> 'a
val snd = fn : 'a * 'b -> 'b
val a_ = fn : 'a * 'b * 'c -> 'a
val b_ = fn : 'a * 'b * 'c -> 'b
val c_ = fn : 'a * 'b * 'c -> 'c
val curry = fn : ('a * 'b -> 'c) -> 'a -> 'b -> 'c
signature TCPSERVER =
sig
type SockAction
type Request
val serve : int
-> (Request
-> (INetSock.inet,Socket.active Socket.stream) Socket.sock
-> SockAction)
-> 'a
end
functor HTTPServer(Buf: sig
type Buffer
val readInto : Buffer
-> ('a,Socket.active Socket.stream)
Socket.sock
-> BufferStatus
val new : int -> Buffer
val toSlice : Buffer -> Word8ArraySlice.slice
val printBuffer : Buffer -> unit
end) :
sig functor <functor> : <fctsig> end
val it = () : unit
... but rejected by mlton.
~/projects/serve-sml $ mlton server.mlb
Error: server.sml 23.1. # (line with "functor Server...")
Syntax error: replacing FUNCTOR with FUN.
Error: server.sml 24.1.
Syntax error: replacing STRUCT with ASTERISK.
Error: server.sml 87.1.
Syntax error found at END.
Error: server.sml 88.0.
Parse error.
...
Additionally, I'm not entirely sure how to use the definition once it's evaluated. Even in smlnj, the obvious fails:
- HTTPServer(DefaultBuffer, DefaultParser) ;
stdIn:1.2-1.12 Error: unbound variable or constructor: HTTPServer
stdIn:2.7-3.1 Error: unbound variable or constructor: DefaultParser
stdIn:1.13-2.5 Error: unbound variable or constructor: DefaultBuffer
-
Can anyone tell me what I'm doing wrong? Or even point me to a good piece of documentation?
Your Server functor does multiple arguments via currying. That does not work in plain SML, because it does not have higher-order functors (which SML/NJ supports as a non-standard extension). You need to use uncurried form, by introducing an auxiliary structure, like you would use a tuple or record in the core language:
functor Server(X : sig structure Buffer : BUFFER; structure Parser : PARSER end) =
...X.Buffer...X.Parser...
structure MyServer =
Server(struct structure Buffer = MyBuffer; structure Parser = MyParser end)
Obviously, this is pretty clumsy and verbose, so at least SML has some syntactic sugar for the above, allowing you to keep the auxiliary structure implicit:
functor Server(structure Buffer : BUFFER; structure Parser : PARSER) =
...Buffer...Parser...
structure MyServer =
Server(structure Buffer = MyBuffer; structure Parser = MyParser)
But that is as short as it gets in current SML.
It's useful to understand that Standard ML is composed of two languages — the core language of values (ordinary functions, numbers, booleans, their types, etc.) and the language of modules, comprised of signatures, structures and functors.
Functors are similar to core functions, they always accept a single argument and return a module-level value. A functor's argument type is specified by a signature, while the actual value of the argument, when "calling" the functor, will be a structure implementing that signature. A functor returns a structure, whose type is again determined by a signature. This is the basic skeleton:
signature ARG = sig end
signature RESULT = sig end
functor FUNCTOR(A : ARG) : RESULT
Now, as mentioned and examplified by Andreas Rossberg, the standard provides some syntactic sugar for expressing a functor's param type. However, I tend to favor the above skeleton when a functor requires more than a few structures as input:
signature SERVER_ARGS =
sig
structure ARG_0 = sig end
structure ARG_1 = sig end
structure ARG_2 = sig end
structure ARG_3 = sig end
end
signature SERVER = sig end
functor ServerFn(ARGS : SERVER_ARGS) : SERVER =
struct
end
Now, when calling a functor, there are several choices as to the syntax:
(* Using an anonymous structure *)
ServerFn(struct
structure ARG_0 = struct end
structure ARG_1 = struct end
structure ARG_2 = struct end
structure ARG_3 = struct end
end)
(* Using a named structure *)
structure ServerArgs =
struct
structure ARG_0 = struct end
structure ARG_1 = struct end
structure ARG_2 = struct end
structure ARG_3 = struct end
end
ServerFn(ServerArgs)
(* Using an anonynous structure, with syntactic sugar *)
ServerFn(
structure ARG_0 = struct end
structure ARG_1 = struct end
structure ARG_2 = struct end
structure ARG_3 = struct end
)
A functor's result, being a structure, may only be found in a structure position in source code, i.e., you either give it a name using the structure keyword, or you pass it along as an argument to some other functor:
structure Server = ServerFn(ServerArgs)
structure Quux = OtherFunctor(ServerFn(ServerArgs))
The structure keyword is the module-level equivalent of the val keyword in the core language. A way to bind "variables" at the module level. In the same vein, the signature keyword is the module-level equivalent of the type keyword in the core language — a helpful way to introduce aliases for anonymous signatures denoted by sig ... end.
This is why your last example fails, because the SML top-level tries to interpret HTTPServer(DefaultBuffer, DefaultParser); as a core-level function call, not as a module-level function/functor call.
I think the StandardML syntax for multi-argument functors is:
signature PARSER = sig
val parse : unit -> unit
end
signature BUFFER = sig
val read : unit -> unit
end
functor Server (structure buffer : BUFFER
structure parser : PARSER) = struct
end
I guess the issue is that SML-NJ supports higher-order functors while MLton does not.

Can we pass functor as an argument to another functor?

I want to know if we can have a local module inside the module. This can be achieved if a functor can be passed as an argument to another functor. But I am not sure if we can do that.
My apologies if this is a vague question.
Thanks.
Yes, it is possible to define higher-order functors. Here is a simple example of a functor that applies its first argument to its second argument:
module App (F : functor (X: sig end) -> sig end) (X: sig end) = F (X)
This is however unrelated to the question of having local modules, which are very straightforward and do not require functors. The following example defines a submodule B that remains private to A:
module A : (sig val g : unit -> unit end) = struct
module B = struct
let f () = print_endline "Hello"
end
let g = B.f
end
let () = A.g () (* valid, prints Hello *)
let () = A.B.f () (* invalid *)

Standard ML: Naming datatypes of function arguments possible?

I'm new to ML and with to have a function that receives a special pre-defined datatype, and able to reference to its entire argument datatype, rather its components.
Here's a stupid example:
datatype frame = Frame of string list * string list
(* Type: fn : string * frame -> frame *)
val lookup_variable_value_in_frame =
fn (string(var), Frame(variables, values)) =>
...
Frame(variables, values)
... ;
1) I want to return the given frame. Must I build another Frame ?
2) I wish to pass the given frame to another function, must I provide a new Frame(variables, values) again ?
I wish I could write somthing like this:
val lookup_variable_value_in_frame =
fn (string(var), frame : Frame(variables, values)) => ...
then I'll be able to use the frame or its components .
Thank you.
Your datatype has already has a name, which is frame. You don't have to build another frame for returning or passing to another function. The first option is using explicit type annotation:
(* Type: fn : string * frame -> frame *)
val lookup_variable_value_in_frame =
fn (var: string, f: frame) =>
...
f
... ;
This option is not common, it should used only when you need types less generic than they are inferred by the type checker. Another option is using as keyword to make another binding to the value:
val lookup_variable_value_in_frame =
fn (var, f as Frame(variables, values)) =>
...(* using f, variables or values here *)
Note that there is no such thing like string(var) in SML, either use var or var: string for explicit type annotation.