Also, about constructing the module itself. We have macros exactly not to use string concatenation for manipulating code:
types = ~w[function nil integer binary bitstring list map float atom tuple pid port reference]
code = for type <- types, fun = :"is_#{type}" do
quote do: def typeof(x) when unquote(fun)(x), do: unquote(type)
end
Module.create(Util, code, Macro.Env.location(__ENV__))
Also binary should be checked before bitstring, since every binary is a bitstring, so otherwise youâd only get bitstrings.
defmodule Util do
types = ~w[function nil integer binary bitstring list map float atom tuple pid port reference]
for type <- types do
def typeof(x) when unquote(:"is_#{type}")(x), do: unquote(type)
end
end
Btw, nil is atom as well. Binaries are also bitstrings. Which is one other reason for avoiding a typeof construct.
Awesome! Thatâs what I want, flexable. I tried to use Macro , and encounter an error, and wonder why.
defmodule Mymacro do
defmacro warp(type) do
quote do
def typeof(x) when unquote(:"is_#{type}")(x), do: unquote(type)
end
end
end
defmodule Util do
import Mymacro
types = ~w[function nil integer binary bitstring list map float atom tuple pid port reference]
for type <- types do
warp(type)
end
end
# error:
** (Protocol.UndefinedError) protocol String.Chars not implemented for {:type, [line: 14], nil}
(elixir) lib/string/chars.ex:3: String.Chars.impl_for!/1
(elixir) lib/string/chars.ex:17: String.Chars.to_string/1
expanding macro: Mymacro.warp/1
elixir.ex:14: Util (module)
warp is macro and as a macro, when you call warp(type) it doesnât see the value of type but rather the AST of type, which is {:type, [line: 14], nil}.
I have played around with runtime data typing and itâs a pretty hard problem once you go beyond the basic Erlang terms.
For perversities sake, I think you can use the Erlang comparision order and create a typer based exclusively on guards and the > operator.
number < atom < reference < fun < port < pid < tuple < map < nil < list < bit string
def is_map(x) when x > @big_tuple and x < nil
Just a silly idea that probably doesnât really work.
I have created a library that will creates a function that takes a single possibly composite term and returns a function that will either return true or false if the data types are âsimilarâ for various definitions of similar.
My use case was to turn single ExUnit assertions into invariants for Property testing.
@bbense: Your is_map returns true for any atom before nil, such as :nik or :a or :my_awesome_long_name.
The problem with those comparisons is that the domains of (most of?) the built-in types is unbound, e.g. there is no smallest number, longest string, biggest tuple, etc. so it becomes (near?) impossible to match for instance any number that is not an atom this way.
Which Erlang terms have a total ordering and which do not? How are functions compared?
iex(6)> foo = fn x -> x end
#Function<6.52032458/1 in :erl_eval.expr/5>
iex(7)> bar = fn x -> x end
#Function<6.52032458/1 in :erl_eval.expr/5>
iex(8)> foo < bar
true
iex(9)> bar < foo
false
iex(10)> foo == bar
false