Background: I am trying to write parser combinators in Dafny. This requires working on very long lists which I do not want to fully compute unless they are needed, so I am using an IList instead of a seq in order to simulate lazy evaluation. The problem which I am having is that I cannot find a way to express an equivalent to forall x in sequence when working with ILists.
I'm defining IList in the same way as Dafny's documentation and tests:
codatatype IList<T> = Nil | Cons(head: T, tail: IList<T>)
I want to define an fmap function over ILists which allows partial functions. Here is my initial (incorrect) implementation:
function method fmap<S, T>(list: IList<S>, fn: S --> T): IList<T>
{
match list
case Nil => Nil
case Cons(s, rest) => Cons(fn(s), fmap(rest, fn))
}
This does not work, because the precondition to fn might not hold, and this is the root problem I'm trying to solve.
I tried to define a copredicate to express the concept of "forall" on infinite lists, and use that:
greatest predicate IListForall<T>(list: IList<T>, fn: T -> bool) {
match list
case Nil => true
case Cons(head, rest) => fn(head) && IListForall(rest, fn)
}
function method fmap<S, T>(list: IList<S>, fn: S --> T): IList<T>
requires IListForall(list, fn.requires)
{ /* body unchanged */ }
This makes fmap verify, but when I try to use fmap I can't find a way to make this precondition satisfied. It comes up when I try to define a mapping function which works on lists containing a certain type:
datatype Container<T> = Container(value: T)
function method fmapContainers<T, U>(cs: IList<Container<T>>, fn: T -> U):
IList<Container<U>>
{
fmap(cs, (container: Container) => Container(fn(container.value)))
}
The invocation of fmap here gives me the error possible violation of function precondition. This doesn't seem right to me. fn is total, and so is the lambda that I'm passing to fmap, so I don't think there should be any precondition in play? I've attempted to write fmapContainers a few different ways with no success, which makes me think that I messed up from the beginning when I tried to express forall as a copredicate.
Is there a better way to express forall than what I did?
footnote: fmapContainers might sound useless, but it's the simplest form of my actual problem. To explain my motivation, here's the full implementation that I'm trying to get working:
datatype OneParse<T> = OneParse(parsed: T, remainder: string)
datatype Result<T> = Failure | Success(forest: IList<OneParse>)
type parser<T> = string -> Result<T>
function method fmapSuccess<S, T>(result: Result<S>, fn: S --> T): Result<T>
requires result.Success?
{
Success(fmap(result.forest,
(one: OneParse<S>) => OneParse(fn(one.parsed), one.remainder)))
}
function method fmapParser<T, U>(p: parser<T>, fn: T --> U): parser<U> {
s => var result := p(s); match result
case Failure => Failure
case Success(forest) => fmapSuccess(result, fn)
}
I think I can figure out how to make the full solution work on my own if someone provides tips for implementing fmap and fmapContents, so this is just for context.
Problem here is greatest predicate (IListForall) is not proved for function (container: Container) => Container(fn(container.value)). This is trivial to prove
greatest lemma IListForallLemma<T, U>(cs: IList<T>, fn: T -> U)
ensures IListForall(cs, fn.requires)
{}
Now following code snippet verifies. I have made fmapContainers to method from function method to call above lemma.
codatatype IList<T> = Nil | Cons(head: T, tail: IList<T>)
greatest predicate IListForall<T>(list: IList<T>, fn: T -> bool)
{
match list
case Nil => true
case Cons(head, rest) => fn(head) && IListForall(rest, fn)
}
function method fmap<S, T>(list: IList<S>, fn: S --> T): IList<T>
requires IListForall(list, fn.requires)
{
match list
case Nil => Nil
case Cons(s, rest) => Cons(fn(s), fmap(rest, fn))
}
datatype Container<T> = Container(value: T)
greatest lemma IListForallLemma<T, U>(cs: IList<T>, fn: T -> U)
ensures IListForall(cs, fn.requires)
{}
method fmapContainers<T, U>(cs: IList<Container<T>>, fn: T -> U) returns (r: IList<Container<U>>)
{
IListForallLemma(cs, (container: Container) => Container(fn(container.value)));
r := fmap(cs, (container: Container) => Container(fn(container.value)));
}
Here Map is a map data consisting of the key and value pairs like
Map = # {"a" => "Apple","b" =>"bat","c" =>"cat ",
"d" => "dog","e" => "eagle","f" => "fan ","g" => "goat",
"h" =>"hat","i" =>"ink","j" =>"jar","k" =>"king","l" =>"lion ",
"m" =>"madam","n" =>"nike","o" => "orange","p" =>"pot",
"q" =>"queue ","r" =>"rat","s" =>"snake","t" =>"tea ",
"u" =>"umbrella","v" =>"van ","w" =>"wolf ","x" =>"xperia ",
"y" =>"yawk","z" =>"zoo "}
I am trying to to print the following below:
Example:
Input: Apple nike goat lion eagle
Expected output: Angle.
Tried code:
-module(main).
-export([start/1,func/2]).
start(Str) ->
Map = # {"a" => "Apple","b" =>"bat","c" =>"cat ","d" => "dog","e" => "eagle","f" => "fan ","g" => "goat","h" =>"hat","i" =>"ink","j" =>"jar","k" =>"king","l" =>"lion ","m" =>"madam","n" =>"nike","o" => "orange","p" =>"pot","q" =>"queue ","r" =>"rat","s" =>"snake","t" =>"tea ","u" =>"umbrella","v" =>"van ","w" =>"wolf ","x" =>"xperia ","y" =>"yawk","z" =>"zoo "},
Chunks = string:tokens(Str, [$\s]),
io:format("~n~p",[Chunks]),
L = func(Chunks,Map),
io:format("~p",[L]).
func([],#{}) ->
io:format("~nCompleted~n");
func([First | Rest], Map) ->
Fun = fun(K, V, Acc) ->
if V == First -> [K | Acc];
true -> Acc
end
end,
maps:fold(Fun, [], Map),
func(Rest, Map).
Anyone please suggest me on this?
You say your input is:
Apple nike goat lion eagle
but that isn't an erlang term, so your input is nonsensical.
If your input is actually the string "Apple nike goat lion eagle", then I'm not sure why you need your map because you can just extract the first letter of each word:
-module(a).
-compile(export_all).
get_first_letters_of_words(Sentence) ->
Words = string:split(Sentence, "\s", all),
Result = get_first_letters(Words, _Acc=[]),
io:format("~p~n", [Result]).
get_first_letters([ [Int|_Ints] | Words], Acc) ->
get_first_letters(Words, [Int|Acc]);
get_first_letters([], Acc) ->
lists:reverse(Acc).
In the shell:
63> c(a).
a.erl:2: Warning: export_all flag enabled - all functions will be exported
{ok,a}
64> a:get_first_letters_of_words("Apple nike goat lion eagle").
"Angle"
ok
You can read about how to work with strings below. The list of words returned by string:split/3 is actually a list where each element is a list of integers, e.g.
[ [97,98,99], [100,101,102] ]
The function get_first_letters/2 might be easier to understand if you write it like this:
get_first_letters([Word|Words], Acc) ->
[Int|_Ints] = Word,
get_first_letters(Words, [Int|Acc]);
get_first_letters([], Acc) ->
lists:reverse(Acc).
It also makes no sense to create a map where the words are the values and the first letters are the keys. Instead, you would create a map where the words are the keys and the values are the first letters, then you could call maps:get/2 with a word to look up the first letter.
If you actually have a map where a word/value is associated with more than one key, then things are a little trickier. The following example retrieves all the keys associated with a word:
-module(a).
-compile(export_all).
get_code_for_words(Sentence) ->
Words = string:split(Sentence, "\s", all),
LettersWords = # {"a" => "Apple",
"b" => "bat",
"c" => "cat",
"x" => "bat"},
Result = get_codes(Words, LettersWords, _AllCodes=[]),
io:format("~p~n", [Result]).
get_codes([Word|Words], Map, AllCodes) ->
NewAllCodes = maps:fold(fun([K],V,Acc) ->
case V =:= Word of
true -> [K|Acc];
_ -> Acc
end
end,
AllCodes,
Map),
get_codes(Words, Map, NewAllCodes);
get_codes([], _Map, AllCodes) ->
lists:reverse(AllCodes).
In the shell:
79> c(a).
a.erl:2: Warning: export_all flag enabled - all functions will be exported
{ok,a}
80> a:get_code_for_words("Apple bat cat").
"abxc"
ok
In erlang, a string is just a shortcut for creating a list of integers, where the integers in the list are the ascii codes of the characters in the string.
8> "abc" =:= [97, 98, 99].
true
You may not like that, but that's the way it is: a double quoted string tells erlang to create a list of integers. The shell is misleading because sometimes the shell prints out the string "abc" as "abc" instead of [97, 98, 99]. To prevent the shell from misleading you, you can execute shell:strings(false) in the shell, and then the shell will always output lists of integers for strings:
12> shell:strings(false).
true
13> "abc".
[97,98,99]
That will continue for the rest of your shell session (or until you execute shell:strings(true). You can still explicitly tell the shell to print a string if you want:
16> io:format("~p~n", ["abc"]).
"abc"
ok
When you use [Head|Tail] to match a list of integers, e.g. a double quoted string, Head will match an integer:
9> [Head|Tail] = "abc".
"abc"
10> Head.
97
The problem is that the keys in your map are strings--not integers. The easiest way to handle that is to turn the integer into a string by inserting it into a list:
11> [Head].
"a"
Here is an example:
-module(a).
-compile(export_all).
get_words(ListOfInts) -> %% ListOfInts can be a double quoted string
LettersWords = # {"a" => "Apple",
"b" => "bat",
"c" => "cat",
"d" => "dog"},
Words = get_words(ListOfInts, LettersWords, _Acc=[]),
io:format("~p~n", [Words]).
get_words([Int|Ints], Map, Acc) ->
Letter = [Int],
Word = maps:get(Letter, Map),
get_words(Ints, Map, [Word|Acc]);
get_words([], _Map, Acc) -> Acc.
In the shell:
3> c(a).
a.erl:2: Warning: export_all flag enabled - all functions will be exported
{ok,a}
4> a:start("cad").
["dog","Apple","cat "]
ok
If you would like the results in the same order as the letters in your input string, then return lists:reverse(Acc) instead of Acc.
If you want to display each word--not a list of words, like this:
"cat" "Apple" "dog"
you can do this:
show_results(Words) ->
lists:foreach(fun(Word) -> io:format("~p ", [Word]) end,
Words),
io:format("~n").
If you don't want to display the quotes, for instance:
cat Apple dog
you can use the ~s control sequence:
show_results(Words) ->
lists:foreach(fun(Word) -> io:format("~s ", [Word]) end,
Words),
io:format("~n").
I propose you this. Of course to be efficient you should store the reverse dictionary somewhere, in a server state for example.
1> % Enter the dictionary
1> Map = # {"a" => "apple","b" =>"bat","c" =>"cat","d" => "dog","e" => "eagle","f" => "fan","g" => "goat","h" =>"hat","i" =>"ink","j" =>"jar","k" =>"king","l" =>"lion","m" =>"madam","n" =>"nike","o" => "orange","p" =>"pot","q" =>"queue","r" =>"rat","s" =>"snake","t" =>"tea","u" =>"umbrella","v" =>"van","w" =>"wolf","x" =>"xperia","y" =>"yawk","z" =>"zoo"}.
#{"a" => "apple","b" => "bat","c" => "cat","d" => "dog",
"e" => "eagle","f" => "fan","g" => "goat","h" => "hat",
"i" => "ink","j" => "jar","k" => "king","l" => "lion",
"m" => "madam","n" => "nike","o" => "orange","p" => "pot",
"q" => "queue","r" => "rat","s" => "snake","t" => "tea",
"u" => "umbrella","v" => "van","w" => "wolf",
"x" => "xperia","y" => "yawk","z" => "zoo"}
2> % You have a dictionary letters to things while you need a dictionary things to letters. Let's revert it
2> L = maps:to_list(Map). % First trasform into list
[{"a","apple"},
{"b","bat"},
{"c","cat"},
{"d","dog"},
{"e","eagle"},
{"f","fan"},
{"g","goat"},
{"h","hat"},
{"i","ink"},
{"j","jar"},
{"k","king"},
{"l","lion"},
{"m","madam"},
{"n","nike"},
{"o","orange"},
{"p","pot"},
{"q","queue"},
{"r","rat"},
{"s","snake"},
{"t","tea"},
{"u","umbrella"},
{"v","van"},
{"w","wolf"},
{"x","xperia"},
{"y","yawk"},
{"z","zoo"}]
3> IL = [{V,K} || {K,V} <- L]. % exchange Key and Values
[{"apple","a"},
{"bat","b"},
{"cat","c"},
{"dog","d"},
{"eagle","e"},
{"fan","f"},
{"goat","g"},
{"hat","h"},
{"ink","i"},
{"jar","j"},
{"king","k"},
{"lion","l"},
{"madam","m"},
{"nike","n"},
{"orange","o"},
{"pot","p"},
{"queue","q"},
{"rat","r"},
{"snake","s"},
{"tea","t"},
{"umbrella","u"},
{"van","v"},
{"wolf","w"},
{"xperia","x"},
{"yawk","y"},
{"zoo","z"}]
4> IM = maps:from_list(IL). % build the expected dictionary
#{"apple" => "a","bat" => "b","cat" => "c","dog" => "d",
"eagle" => "e","fan" => "f","goat" => "g","hat" => "h",
"ink" => "i","jar" => "j","king" => "k","lion" => "l",
"madam" => "m","nike" => "n","orange" => "o","pot" => "p",
"queue" => "q","rat" => "r","snake" => "s","tea" => "t",
"umbrella" => "u","van" => "v","wolf" => "w",
"xperia" => "x","yawk" => "y","zoo" => "z"}
5> Input = ["apple", "nike", "goat", "lion", "eagle"]. % define a test input
["apple","nike","goat","lion","eagle"]
6> lists:reverse(lists:foldl(fun(X,Acc) -> [V] = maps:get(X,IM), [V|Acc] end, [], Input)). % translate
"angle"
7>