Search code examples
recursiontypessml

SML, recursing a datatype array


I have this datatype

datatype json =
    Num of real
    | String of string
    | False
    | True
    | Null
    | Array of json list
    | Object of (string * json) list

and this code

fun mymatch (jobj,str) = 
    case jobj of
        Array [] => NONE
      | Array(Object oh::[]) => mymatch (Object oh, str)
      | Array (Object oh::ot) => 
           if isSome (assoc(str, oh)) then assoc(str, oh) else mymatch (Array ot, str)
      | Object xs => assoc (str, xs)
      | _ => NONE

with this helper function

fun assoc (k, ls) =
    case ls of
        [] => NONE
      | (a,b)::[] => if k = a then SOME b else NONE
      | (a,b)::xs => if k = a then SOME b else assoc (k,xs)

which should take something like this

mymatch (Array [Object [("n", Num (4.0)),("b", True)],Object [("last", Num (4.0)),("foo", True)]],"foo")

and return a match on the string "foo", searching each Object in the Array. As you can see in the code, I'm really only handling the two things in json that qualify for a match, i.e., an Array that contains Objectss, then sending the Objects to be checked. This code works, but it's surely from the brutalist school of programming, i.e., it feels like a kludge. Why? Because of the case in mymatch where I need to recurse down through the Array

...
| Array (Object oh::ot) => 
     if isSome (assoc(str, oh)) then assoc(str, oh) else mymatch (Array ot, str)
...

Until now, I've only dealt with recursion on lists where you check the car, then recurse on the cdr. Again, this code works, but I can sense I'm missing something. I need to check the head Object of Array and terminate if it matches; otherwise, keep recursing -- all within the option return world. Is there a more elegant way of doing this?


Solution

  • Write a function for matching the "innards" of arrays:

    fun match_array (str, Object ob :: obs) = (case assoc (str, ob) of
                                                   NONE => match_array (str, obs)
                                                 | something => something)
      | match_array _ = NONE;
    

    then rewrite mymatch:

    fun mymatch (str, Array a) = match_array (str, a)
      | mymatch (str, Object ob) = assoc (str, ob)
      | mymatch _ = NONE;
    

    You can also simplify assoc a little bit:

    fun assoc (k, []) = NONE
      | assoc (k, (a,b)::xs) = if k = a then SOME b else assoc (k,xs);