I’m not sure how to “configure” a first-class module using run-time values. Here is a simple example. A quiz can be dynamically configured when it is initialized. Some quizzes might take a configuration parameter for the number/difficulty of questions and other quizzes might be configured by topic. The configuration is “generic” - it differs by quiz. These examples here don’t compile because the compiler is expecting the let initialize
to be generic but I’m hardwiring the type. I can get this to work using functors. The FancyQuiz
could be built using a functor with a configuration module like let count = 23; let difficulty = "hard"
but I want these parameters to be dynamic; the user should be able to type it in.
module type QuizLogic = {
type t
let initialize: 'configuration => t
let questions: t => array<string>
}
module EmptyQuiz: QuizLogic = {
type t = array<string>
let initialize = () => []
let questions = (i: t) => i
}
module FancyQuiz: QuizLogic = {
type t = array<string>
let initialize = ((count, difficulty)) =>
switch difficulty {
| "hard" => Array.makeBy(count, i => `Hard Question # ${i->Int.toString}`)
| _ => Array.makeBy(count, i => `Easy Question # ${i->Int.toString}`)
}
let questions = (i: t) => i
}
Here is another example of something similar that also won’t compile.
module type MathTypeA = {
type domain
let add: (domain, domain) => domain
let subtract: (domain, domain) => domain
let zero: domain
}
module IntMath: MathTypeA = {
type domain = int
let add = (x, y) => x + y
let subtract = (x, y) => x - y
let zero = 0
}
// compile error
let doubleA = (~val: 'x, ~math: module(MathType with type domain := 'x)) => {
let module(Math) = math
Math.add(val, val)
}
// using the module with a value that is specific to the IntMath implementation
doubleA(4, moduel(IntMath))
I’m having trouble formulating my question here and getting my head around this. I’m looking for some kind of “generic” module but can’t figure out how to do it. I want the flexibility of first-class modules, where you can swap out one implementation for another, but also want to customize them dynamically rather than using functors where everything has to be statically defined. In the first example above, I tried using a generic parameter in the module type - let initialize
- but that didn’t work. In the second example, I tried using an abstract type - type domain
- but that didn’t work in my doubleA
function.
=== UPDATE ===
Here is something closer to what I’m actually trying to build. I’ve got function giveQuiz
that is supposed to take a module matching the QuizLogic
type AND a configuration for that particular kind of quiz and then do something with it. This giveQuiz
function is flexible because I can plug in any kind of quiz (each quiz can have a different set of configuration properties) and customize the parameters of the quiz by passing different values into the function. This doesn’t compile.
module type QuizLogic = {
type quiz
type configuration
let make: configuration => quiz
let questions: quiz => array<string>
}
module FancyQuiz: QuizLogic = {
type quiz = array<string>
type configuration = {
difficultyLevel: int,
repeatQuestionIfWrongAnswer: bool,
}
let make = (_config: configuration) => [] // would use the config for this
let questions = (q: quiz) => q
}
// Syntax error : The type of this packed module contains variables.
let giveQuiz = (config: 'a, quizLogic: module(QuizLogic with type configuration = 'a)) => {
let module(Q) = quizLogic
switch Q.make(config)->Q.questions {
| [] => "No questions!"
| xs => "The quiz has some questions!"
}
}