Search code examples
stringlistdictionarysmlnj

How exactly have I mangled this attempt at using a map in Standard ML?


I'm coding a caesar cipher in Standrd ML for a class assignment. I'm familiar enough with functional programming to know that a map is probably what I want here. I'm having trouble understanding the way the map function is implemented in Standard ML.

fun chrEnc (letter:char, key:int): char = 
    if Char.isAlpha(letter) then 
        if Char.isUpper(letter) then 
            chr (ord #"A" + (ord letter - ord #"A" - key) mod 26)
        else  chr (ord #"a" + (ord letter - ord #"z" - key) mod 26)
    else letter;

fun chrDec(letter:char, key:int): char  =
    chrEnc(letter,  (~1 * key));

fun msgEnc(message:string, key:int): string  =
    implode (map (fn x => chrEnc(x,key)) (explode message));

You can probably guess that the expected behavior of msgEnc is to take a message and a key, and iterate over the message, executing chrEnc(x key) where x is the current value in the string message and return the mapped string. Expected output from msgEnc("IBM",1); should be "HAL". However, it's "HBM", which makes it look like it's only operating on the first character. I thought maybe using (explode message) as the list parameter for map might be the issue so I tried:

fun msgEnc(message:string, key:int): string  =
 val msg = explode message;
 implode (map (fn x => chrEnc(x,key)) msg);  

but the REPL barfs on the val, saying:

= = = = = [autoloading]
[library $SMLNJ-BASIS/basis.cm is stable]
[autoloading done]
val chrEnc = fn : char * int -> char
- - = val chrDec = fn : char * int -> char
- - = stdIn:27.2 Error: syntax error found at VAL

Not sure what I'm missing, my assumption is that I'm misunderstanding the syntax or usage of map. I had never seen ML before yesterday, so I'm a nit noobish.


Solution

  • First of all, your original code actually seemed to work for me, so maybe try recompiling or running your code in a fresh REPL:

    - msgEnc("IBM", 1);
    val it = "HAL" : string
    

    The problem with your second bit of code is just a syntax error. You can't have plain val bindings inside a function scope like that, only at the top-level. For variables local to a function, use let:

    fun msgEnc (message:string, key:int) : string =
        let
           val msg = explode message
        in
           implode (map (fn x => chrEnc(x, key)) msg)
        end
    

    This is actually completely equivalent to your original code, since the only difference is the local msg binding.

    Standard ML is very expression-oriented: the body of a function must be a single expression. You have what looks like two statements, but the val binding isn't a statement. You can use the semicolon and parens to sequence expressions, but this is only really useful for code with side effects, for instance:

    let
        val name = "nick"
    in
        (print "hi "; print name)
    end
    

    This whole thing is an expression too, where the value of the let expression is the last expression in the (...; ...; ...) sequence. In fact, none of the semicolons in your code are actually necessary.

    Hope that helps!