How does one specify a forward reference in Erlang? - recursion

I have been plaing around with Erlang, and decided to try to make a directory lister. After hacking some code together I hit as road block, as the second commented line shows an error message. Literally it can't see the iterate function. I've done a bit of research here and on google. I have tried exporting the functions as well. There is something here that I am not thinking about correctly. Can someone point me in the correct direction?
-module(iterate_dir).
% exporting iterate/1 does not make it visible.
-export([start/0, iterate/1, show_files/2]).
show_files([], _) ->
ok;
show_files([Head|Tail], Path) ->
FullPath = [Path] ++ [Head],
case filelib:is_dir(FullPath) of
% function iteratate/1 undefined
true -> io:format("Dir ~s\n", [FullPath]), iteratate(FullPath);
false-> io:format("File ~s\n", [FullPath])
end,
show_files(Tail, Path).
iterate(Directory) ->
case file:list_dir(Directory) of
{ok, Files} -> show_files(Files, Directory);
{error, Reason} -> io:format("Error ~s~n", [Reason])
end.
start() ->
io:format("Running~n"),
iterate("c:\\"),
io:format("Complete~n").

The function is called "iterate", you are calling it as "iteratate"
notice the extra "at" in the middle at the call site (and comment)

Related

Dialyzer does not catch errors on returned functions

Background
While playing around with dialyzer, typespecs and currying, I was able to create an example of a false positive in dialyzer.
For the purposes of this MWE, I am using diallyxir (versions included) because it makes my life easier. The author of dialyxir confirmed this was not a problem on their side, so that possibility is excluded for now.
Environment
$ elixir -v
Erlang/OTP 24 [erts-12.2.1] [source] [64-bit] [smp:12:12] [ds:12:12:10] [async-threads:1] [jit]
Elixir 1.13.2 (compiled with Erlang/OTP 24)
Which version of Dialyxir are you using? (cat mix.lock | grep dialyxir):
"dialyxir": {:hex, :dialyxir, "1.1.0", "c5aab0d6e71e5522e77beff7ba9e08f8e02bad90dfbeffae60eaf0cb47e29488", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "07ea8e49c45f15264ebe6d5b93799d4dd56a44036cf42d0ad9c960bc266c0b9a"},
"erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"},
Current behavior
Given the following code sample:
defmodule PracticingCurrying do
#spec greater_than(integer()) :: (integer() -> String.t())
def greater_than(min) do
fn number -> number > min end
end
end
Which clearly has a wrong typing, I get a success message:
$ mix dialyzer
Compiling 1 file (.ex)
Generated grokking_fp app
Finding suitable PLTs
Checking PLT...
[:compiler, :currying, :elixir, :gradient, :gradualizer, :kernel, :logger, :stdlib, :syntax_tools]
Looking up modules in dialyxir_erlang-24.2.1_elixir-1.13.2_deps-dev.plt
Finding applications for dialyxir_erlang-24.2.1_elixir-1.13.2_deps-dev.plt
Finding modules for dialyxir_erlang-24.2.1_elixir-1.13.2_deps-dev.plt
Checking 518 modules in dialyxir_erlang-24.2.1_elixir-1.13.2_deps-dev.plt
Adding 44 modules to dialyxir_erlang-24.2.1_elixir-1.13.2_deps-dev.plt
done in 0m24.18s
No :ignore_warnings opt specified in mix.exs and default does not exist.
Starting Dialyzer
[
check_plt: false,
init_plt: '/home/user/Workplace/fl4m3/grokking_fp/_build/dev/dialyxir_erlang-24.2.1_elixir-1.13.2_deps-dev.plt',
files: ['/home/user/Workplace/fl4m3/grokking_fp/_build/dev/lib/grokking_fp/ebin/Elixir.ImmutableValues.beam',
'/home/user/Workplace/fl4m3/grokking_fp/_build/dev/lib/grokking_fp/ebin/Elixir.PracticingCurrying.beam',
'/home/user/Workplace/fl4m3/grokking_fp/_build/dev/lib/grokking_fp/ebin/Elixir.TipCalculator.beam'],
warnings: [:unknown]
]
Total errors: 0, Skipped: 0, Unnecessary Skips: 0
done in 0m1.02s
done (passed successfully)
Expected behavior
I expected dialyzer to tell me the correct spec is #spec greater_than(integer()) :: (integer() -> bool()).
As a side note (and comparison, if you will) gradient does pick up the error.
I know that comparing these tools is like comparing oranges and apples, but I think it is still worth mentioning.
Questions
Is dialyzer not intended to catch this type of error?
If it should catch the error, what can possibly be failing? (is it my example that is incorrect, or something inside dialyzer?)
I personally find it hard to believe this could be a bug in Dialyzer, the tool has been used rather extensively by a lot of people for me to be the first to discover this error. However, I cannot explain what is happening.
Help is appreciated.
Dialyzer is pretty optimistic in its analysis and ignores some categories of errors.
This article provides some advanced explanations about its approach and limitations.
In the particular case of anonymous functions, dialyzer seems to perform a very minimal check
when they are being declared: it will ignore both the types of its arguments and return type, e.g.
the following doesn't lead any error even if is clearly wrong:
# no error
#spec add(integer()) :: (String.t() -> String.t())
def add(x) do
fn y -> x + y end
end
It will however point out a mismatch in arity, e.g.
# invalid_contract
# The #spec for the function does not match the success typing of the function.
#spec add2(integer()) :: (integer(), integer() -> integer())
def add2(x) do
fn y -> x + y end
end
Dialyzer might be able to detect a type conflict when trying to use the anonymous function,
but this isn't guaranteed (see article above), and the error message might not be helpful:
# Function main/0 has no local return.
def main do
positive? = greater_than(0)
positive?.(2)
end
We don't know what is the problem exactly, not even the line causing the error. But at least we know there is one and can debug it.
In the following example, the error is a bit more informative (using :lists.map/2 instead of Enum.map/2 because
dialyzer doesn't understand the enumerable protocol):
# Function main2/0 has no local return.
def main2 do
positive? = greater_than(0)
# The function call will not succeed.
# :lists.map(_positive? :: (integer() -> none()), [-2 | 0 | 1, ...])
# will never return since the success typing arguments are
# ((_ -> any()), [any()])
:lists.map(positive?, [1, 0, -2])
end
This tells us that dialyzer inferred the return type of greater_than/1 to be (integer() -> none()).
none is described in the article above as:
This is a special type that means that no term or type is valid.
Usually, when Dialyzer boils down the possible return values of a function to none(), it means the function should crash.
It is synonymous with "this stuff won't work."
So dialyzer knows that this function cannot be called successfully, but doesn't consider it to be a type clash until actually called, so it will allow the declaration (in the same way you can perfectly create a function that just raises).
Disclaimer: I couldn't find an official explanation regarding how dialyzer handles anonymous
functions in detail, so the explanations above are based on my observations and interpretation

Storing user input information in functional programming (Erlang) using only immutable variables

As a beginner in Erlang, I am working my way through the Programming Erlang book (2nd ed). I have a very hard time grasping how to store and periodically update external information (such as intermittent user input) using the principles of functional programming exclusively.
To take my present example, I am now in the beginning of the concurrent programming section (Chapter 12) where the book talks about the area server. Below is my variant of it.
As an exercise, I am trying to add to this module a way to store all the requests the user makes. But despite having a bit of experience with recursive programming the lack of mutable variables, in the sense of imperative languages, seems to be crippling in this particular instance.
I have tried looking up a few related resources on SE sites such as mutable state in functional programming and
immutability in fp but it doesn't really answer my question in a practical way. I know that what I am trying to accomplish can be done by use of the ETS (or even a database), or by using the process-memory of a new process which receives and maintains the history within itself.
But what I would really like to understand (and the point of this question) is if this can be accomplished using generic functional programming principles without having to use Erlang-specific tools. The commented out lines in the code segment indicate what I am naively expecting the first steps to look like.
-module(geometry_server4).
-export([start/0, client/2, loop/0]).
start() ->
spawn(geometry_server4, loop, []).
client(Pid_server, Geom_tuple) ->
Pid_server ! {self(), Geom_tuple},
%ok = storerequests(Geom_tuple),
receive
{area, Pid_server, Area} -> io:format("Client: Area of ~p is ~p~n", [Geom_tuple, Area]);
{error, Error} -> io:format("~p~n", [Error])
end.
%storerequests(Geom_tuple) -> addtolist(Geom_tuple, get_history()).
%
%addtolist(Item, History) ->
% [Item | History].
%get_history() -> ???
loop() ->
receive
{Client, {rectangle, S1, S2}} ->
Area = S1 * S2,
Client ! {area, self(), Area},
loop();
{Client, {square, S}} ->
Area = S * S,
Client ! {area, self(), Area},
loop();
{Client, _} ->
Client ! {error, "invalid parameters"},
loop()
end.
Based on the book, this toy server gets called in the terminal as:
1> c(geometry_server4).
2> P = geometry_server4:start().
3> geometry_server4:client(P, {square, 3}).
But what I would really like to understand (and the point of this
question) is if this can be accomplished using generic functional
programming principles without having to use Erlang-specific tools.
Yes, it can. You can use a loop variable to store what's known as the state.
First, a couple of preliminary points:
Don't post code with line numbers. You want someone to be able to copy your code and paste it in their text editor and be able to run the code.
In erlang, by convention you use camel case for variable names, such as ServerPid.
For your own sanity, don't use module names that are more than two letters long.
Consider putting all your server code in one portion of the file, and all the client code in another portion of the file. Your client code is in the middle of the server code.
-module(my).
%%-export([setup/1]).
-compile(export_all).
%%-include_lib("eunit/include/eunit.hrl").
%%
start() ->
spawn(my, loop, [[]]).
loop(History) ->
receive
{Client, {rectangle, S1, S2}=Tuple} ->
Area = S1 * S2,
Client ! {area, self(), Area},
loop([Tuple|History]); %Add Tuple to the history
{Client, {square, S}=Tuple} ->
Area = S * S,
Client ! {area, self(), Area},
loop([Tuple|History]);
{Client, history} ->
Client ! {history, self(), History},
loop([history|History]);
{Client, Other} ->
Client ! {error, self(), "invalid parameters"},
loop([{error, Other}|History])
end.
client(ServerPid, Req) ->
ServerPid ! {self(), Req},
receive
Reply -> io:format("~p~n", [Reply])
end.
test() ->
ServerPid = start(),
Requests = [
{rectangle, 2, 3},
{square, 4},
history,
"hello",
history
],
send_requests(Requests, ServerPid).
send_requests([], _) ->
done;
send_requests([Req|Reqs], ServerPid) ->
client(ServerPid, Req),
send_requests(Reqs, ServerPid).
In the shell:
1> c(my).
{ok,my}
2> my:test().
{area,<0.64.0>,6}
{area,<0.64.0>,16}
{history,<0.64.0>,[{square,4},{rectangle,2,3}]}
{error,<0.64.0>,"invalid parameters"}
{history,<0.64.0>,[{error,"hello"},history,{square,4},{rectangle,2,3}]}
done
3>

How can I get this function to be tail-recursive?

I'm still trying to implement 2-3 finger trees and I made good progress (repository). While doing some benchmarks I found out that my quite basic toList results in a StackOverflowException when the tree ist quite large. At first I saw an easy fix and made it tail-recursive.
Unfortunately, it turned out that toList wasn't the culprit but viewr was:
/// Return both the right-most element and the remaining tree (lazily).
let rec viewr<'a> : FingerTree<'a> -> View<'a> = function
| Empty -> Nil
| Single x -> View(x, lazyval Empty)
| Deep(prefix, deeper, One x) ->
let rest = lazy (
match viewr deeper.Value with
| Nil ->
prefix |> Digit.promote
| View (node, lazyRest) ->
let suffix = node |> Node.toList |> Digit.ofList
Deep(prefix, lazyRest, suffix)
)
View(x, rest)
| Deep(prefix, deeper, Digit.SplitLast(shorter, x)) ->
View(x, lazy Deep(prefix, deeper, shorter))
| _ -> failwith Messages.patternMatchImpossible
Looking for the only recursive call it is obvious that this is is not tail-recursive. Somehow I hoped this problem wouldn't exist because that call is wrapped in a Lazy which IMHO is similar to a continuation.
I heard and read of continuations but so far never (had to) use(d) them. I guess here I really need to. I've been staring at the code for quite some time, putting function parameters here and there, calling them other places… I'm totally lost!
How can this be done?
Update: The calling code looks like this:
/// Convert a tree to a list (left to right).
let toList tree =
let rec toList acc tree =
match viewr tree with
| Nil -> acc
| View(head, Lazy tail) -> tail |> toList (head::acc)
toList [] tree
Update 2: The code that caused the crash is this one.
let tree = seq {1..200000} |> ConcatDeque.ofSeq
let back = tree |> ConcatDeque.toList
The tree get built fine, I checked and it is only 12 levels deep. It's the call in line 2 that triggered the overflow.
Update 3: kvb was right, that pipe issue I ran into before has something to do with this. Re-testing the cross product of debug/release and with/without pipe it worked in all but one case: debug mode with the pipe operator crashed. The behavior was the same for 32 vs. 64 bit.
I'm quite sure that I was running release mode when posting the question but today it's working. Maybe there was some other factor… Sorry about that.
Although the crash is solved, I'm leaving the question open out of theoretical interest. After all, we're here to learn, aren't we?
So let me adapt the question:
From looking at the code, viewr is definitely not tail-recursive. Why doesn't it always blow up and how would one rewrite it using continuations?
Calling viewr never results in an immediate recursive call to viewr (the recursive call is protected by lazy and is not forced within the remainder of the call to viewr), so there's no need to make it tail recursive to prevent the stack from growing without bound. That is, a call to viewr creates a new stack frame which is then immediately popped when viewr's work is done; the caller can then force the lazy value resulting in a new stack frame for the nested viewr call, which is then immediately popped again, etc., so repeating this process doesn't result in a stack overflow.

Erlang: Prompt until User chooses to Exit

I'm new to Erlang.
My problem though is I can't seem to figure out how to create functions that will continue to prompt a user until they enter some kind of "exit" button.
I made a very simple program below that I thought would exit if the user typed "4", but I thought wrong:
reprompt() ->
{ok, X} = io:fread("Prompt>", "~s"),
case X of
4 -> io:format("exit");
_ -> reprompt()
end.
I've tried other variations, but most of followed the same or similar pattern.
Any suggestions?
io:fread/2 returns a list. With the format "~s", it will return a list containing one string. If you want to input a single number into X, you can do:
{ok, [X]} = io:fread("Prompt>", "~d"),
Note that this will crash if the user enters something other than a number. You should use a case expression and add clauses for {ok, [X]} and {error, Reason}.

Erlang Hash Tree

I'm working on a p2p app that uses hash trees.
I am writing the hash tree construction functions (publ/4 and publ_top/4) but I can't see how to fix publ_top/4.
I try to build a tree with publ/1:
nivd:publ("file.txt").
prints hashes...
** exception error: no match of right hand side value [67324168]
in function nivd:publ_top/4
in call from nivd:publ/1
The code in question is here:
http://github.com/AndreasBWagner/nivoa/blob/886c624c116c33cc821b15d371d1090d3658f961/nivd.erl
Where do you think the problem is?
Thank You,
Andreas
Looking at your code I can see one issue that would generate that particular exception error
publ_top(_,[],Accumulated,Level) ->
%% Go through the accumulated list of hashes from the prior level
publ_top(string:len(Accumulated),Accumulated,[],Level+1);
publ_top(FullLevelLen,RestofLevel,Accumulated,Level) ->
case FullLevelLen =:= 1 of
false -> [F,S|T]=RestofLevel,
io:format("~w---~w~n",[F,S]),
publ_top(FullLevelLen,T,lists:append(Accumulated,[erlang:phash2(string:concat([F],[S]))]),Level);
true -> done
end.
In the first function declaration you match against the empty list. In the second declaration you match against a list of length (at least) 2 ([F,S|T]). What happens when FullLevelLen is different from 1 and RestOfLevel is a list of length 1? (Hint: You'll get the above error).
The error would be easier to spot if you would pattern match on the function arguments, perhaps something like:
publ_top(_,[],Accumulated,Level) ->
%% Go through the accumulated list of hashes from the prior level
publ_top(string:len(Accumulated),Accumulated,[],Level+1);
publ_top(1, _, _, _) ->
done;
publ_top(_, [F,S|T], Accumulated, Level) ->
io:format("~w---~w~n",[F,S]),
publ_top(FullLevelLen,T,lists:append(Accumulated,[erlang:phash2(string:concat([F],[S]))]),Level);
%% Missing case:
% publ_top(_, [H], Accumulated, Level) ->
% ...

Resources