I’m not looking for production-grade code, mainly is it idiomatic or is there more idiomatic way to write that function computing the Caesar Cipher of a string?
expect caesar "abc" 1 == Ok "bcd"
expect caesar "abc" 25 == Ok "zab"
caesar : Str, U8 -> Result Str _
caesar = \plaintext, shift ->
Str.walkUtf8 plaintext [] \acc, letter ->
List.append acc ((letter - 'a' + shift) % 26 + 'a')
|> Str.fromUtf8
I can't answer your question regarding whether the following may be more idiomatic or not, but if your expected input is only ASCII, you might consider also (in pseudocode):
List U8
.shift
.I suspect this version might be a tiny bit more readable (as you wouldn't need to do the element-wise shifting explicitly).
Indeed that’s more readable to convert to List U8 first, thanks @Hristo
caesar : Str, U8 -> Result Str _
caesar = \plaintext, shift ->
Str.toUtf8 plaintext
|> List.map \c -> (c - 'a' + shift) % 26 + 'a'
|> Str.fromUtf8
I’m not sure what you meant by split and concat though
The list version should also be a lot more performant
It should happen inplace
@Matthieu Pizenberg you're absolutely right!
I’m not sure what you meant by split and concat though
I wasn't being clear at all in my message above. But I'm also glad that - despite my confusing pseudocode - you arrived at a version which you feel is better.
I was thinking in terms of the Caesar cipher being an explicit mapping, like so:
alphabet = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z']
ceasar : Str, U8 -> Str
ceasar = \plaintext, shift ->
mapChar = ceasarHelp shift
Str.toUtf8 plaintext
|> List.map mapChar
|> Str.fromUtf8
|> Result.withDefault ""
ceasarHelp : U8 -> (U8 -> U8)
ceasarHelp = \shift ->
{ before, others } = List.split alphabet (Num.toU64 shift)
shifted = List.concat others before
zipped = List.map2 alphabet shifted Pair
dict = List.walk zipped (Dict.empty {}) \mapping, (Pair input output) ->
Dict.insert mapping input output
\input -> Dict.get dict input |> Result.withDefault 0
expect ceasar "abc" 1 == "bcd"
expect ceasar "abc" 25 == "zab"
Clearly, this isn't as readable - I have to admit that prior to writing it out, the mapping part seemed (in my mind) it'd be neater to implement and subsequently read.
Thanks @Brendan Hansknecht for jumping in to confirm regarding idiomaticity and efficiency!
Right! yes I was missing the alphabet part. I like that version too thanks for that. Not as terse but shows more of the language
This last version will probably be a lot slower. Dict in the hot loop will be a lot slower than a little bit of math.
Yeah, that's true! :100:
It might be more useful in cases where arbitrary mappings are needed (instead of something that could be computed arithmetically).
Right, that’s more useful in settings where the substitution key of the cipher is arbitrary
Last updated: Jul 06 2025 at 12:14 UTC