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.
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!