|
| 1 | +(* Implementation using a final-tagless representation. |
| 2 | + * This is somewhat common among DSL-solutions in OCaml |
| 3 | + *) |
| 4 | + |
| 5 | +module JSON = struct |
| 6 | + (* This part implements JSON output. A real solution is probably |
| 7 | + * using something like YoJson or likewise here. It serves as the output |
| 8 | + * type as well |
| 9 | + *) |
| 10 | + type t = |
| 11 | + | JString of string |
| 12 | + | JInt of int |
| 13 | + | JList of t list |
| 14 | + | JBool of bool |
| 15 | + | JDict of (string * t) list |
| 16 | +end |
| 17 | + |
| 18 | +(* Define the data at the type-level as functions you compose together. |
| 19 | + * |
| 20 | + * Defining a parser from a concrete structure into these functions is an |
| 21 | + * exercise left for the reader. |
| 22 | + *) |
| 23 | +module type SYM = sig |
| 24 | + type out = JSON.t |
| 25 | + type 'a repr |
| 26 | + type lesson |
| 27 | + type chapter |
| 28 | + type db |
| 29 | + val lesson : string -> lesson repr |
| 30 | + val chapter : string -> bool -> (lesson repr) list -> chapter repr |
| 31 | + val db: (chapter repr) list -> db repr |
| 32 | + |
| 33 | + val eval : db repr -> out |
| 34 | +end |
| 35 | + |
| 36 | +(* The example, given as abstract data. Note that we define this as a functor, |
| 37 | + * so we don't say how the functions db, chapter, lesson, ... are evaluated |
| 38 | + *) |
| 39 | +module Ex1(S: SYM) = struct |
| 40 | + open S |
| 41 | + let ex1 = db [chapter "Getting started" false [lesson "Welcome"; lesson "Installation"]; |
| 42 | + chapter "Basic operator" false [lesson "Addition / Subtraction"; lesson "Multiplication / Division"]; |
| 43 | + chapter "Advanced topics" true [lesson "Mutability"; lesson "Immutability"]] |
| 44 | + let ex1_eval = eval ex1 |
| 45 | +end |
| 46 | + |
| 47 | +(* Provide an evaluation interpreter over the structure *) |
| 48 | +module Eval : SYM = struct |
| 49 | + type out = JSON.t |
| 50 | + (* The representation here is key. It states we thread the positional arguments around. |
| 51 | + * A more CPS-like variant are also possible here by manipulating this type *) |
| 52 | + type 'a repr = int -> int -> (int * int * 'a) |
| 53 | + type lesson = JSON.t |
| 54 | + type chapter = JSON.t |
| 55 | + type db = JSON.t |
| 56 | + |
| 57 | + let lesson l i j = (i, j+1, JSON.JDict [("name", JString l); ("position", JInt j)]) |
| 58 | + |
| 59 | + (* Unexported helper for a list of lessons *) |
| 60 | + let lessons (ls: lesson repr list) i j = |
| 61 | + let (final_i, final_j, xs) = |
| 62 | + (List.fold_left |
| 63 | + (fun (i, j, acc) less -> let (i', j', out) = less i j in (i', j', out::acc)) |
| 64 | + (i,j, []) |
| 65 | + ls) |
| 66 | + in (final_i, final_j, JSON.JList (List.rev xs)) |
| 67 | + |
| 68 | + (* The following two functions make aggressive use of binding shadowing. It's intended *) |
| 69 | + let chapter title reset ls i j = |
| 70 | + let j = if reset then 1 else j in |
| 71 | + let (i, j, less_out) = lessons ls i j |
| 72 | + in (i+1, j, JSON.JDict [("title", JString title); ("position", JInt i); ("reset_lesson_position", JBool reset); ("lessons", less_out)]) |
| 73 | + |
| 74 | + let db (chapters: (chapter repr) list) i j = |
| 75 | + let rec loop cs i j acc = |
| 76 | + match cs with |
| 77 | + | [] -> (i, j, List.rev acc) |
| 78 | + | c::next -> |
| 79 | + let (i, j, d) = c i j in loop next i j (d::acc) in |
| 80 | + let (i, j, xs) = loop chapters i j [] |
| 81 | + in (i, j, JSON.JList xs) |
| 82 | + |
| 83 | + let eval db = |
| 84 | + let (_, _, res) = db 1 1 in res |
| 85 | +end |
| 86 | + |
0 commit comments