Search code examples
recordsmlsmlnj

Avoiding explicit record typing


Suppose I have the following functions for operating over an idealized stream:

fun Stream s = { pos = 0, row = 1, col = 0, str = s }
fun len { str, pos = _, row = _, col = _ } = String.size str

fun poke off { str, pos, row: int, col: int } =
  let val n = pos + off in
    if n >= 0 andalso n <= (String.size str) then SOME (String.sub(str, n)) else NONE
  end

This works/compiles, but it's unfortunate to have to litter my function definitions with information I don't care about. row/col are ignored poke and len. However, while the wildcard can be used with len, it can't be with poke. Is there a way to restructure these functions so that less explicit typing needs to be put in, while still being able to pattern match/destructure?


Solution

  • If you give your type a name (such as stream), you can refer to it more briefly:

    type stream = { pos : int, row : int, col : int, str : string }
    
    fun Stream s = { pos = 0, row = 1, col = 0, str = s }
    
    fun len ({ str, ... } : stream) = String.size str
    
    fun poke off ({ str, pos, ... } : stream) =
      let val n = pos + off in
        if n >= 0 andalso n <= String.size str
        then SOME (String.sub (str, n))
        else NONE
      end
    

    Or, more-or-less equivalently:

    datatype stream = STREAM of { pos : int, row : int, col : int, str : string }
    
    fun Stream s = STREAM { pos = 0, row = 1, col = 0, str = s }
    
    fun len (STREAM { str, ... }) = String.size str
    
    fun poke off (STREAM { str, pos, ... }) =
      let val n = pos + off in
        if n >= 0 andalso n <= String.size str
        then SOME (String.sub (str, n))
        else NONE
      end