Just created a "typeof" module

It seams there is no such mudule, so I create one. a little ugly.:grinning:

s = "defmodule Util do\n"
s2 = ["function", "nil", "integer", "bitstring", "list", "map", "float", "atom", "binary", "tuple", "pid", "port", "reference"]
|> Enum.map(fn(type) -> "def typeof(a) when is_#{type}(a), do: \"#{type}\"" end)
|> Enum.join("\n")

#then eval it
Code.eval_string s <> s2 <> "end"

# usage:
Util.typeof {} 
#=> "tuple"
Util.typeof 9 
#=> "integer"

I’d prefer to have the function return tuples instead of strings, also you should do a proper module and use macros for code generation.

But aside of that, I do like the idea.

1 Like

The reason that this does not exist is that it is generally far more idiomatic to just match for what you want. In otherwords instead of doing:

def foo(item) do
  if typeof(item) == :list do
    "it's a list"
  else
    "it's not a list"
  end
end

it’s just better to do

def foo(list) when is_list(list), do: "it's a list"
def foo(_), do: "it's not a list"
4 Likes

Or this:

def foo(item) do
  case item do
    l when is_list(l) -> "it's a list"
    _ -> "it's not a list"
  end
end

If you want something more in-function, though matching in heads is generally always preferred as @benwilson512 shows. :slight_smile:

2 Likes

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.

10 Likes

You don’t even need Module.create:

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.

12 Likes

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)
1 Like

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

1 Like

The problem is that these types are not mutually exclusive. nil is an atom, but typeof(nil) != :atom.

1 Like

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.

1 Like

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

1 Like

Yeah, I was just trying to be funny. But I pulled that list from the Erlang operator page and just playing around
I found this

iex(1)> %{} < nil
false
iex(2)> [1] > nil
true
iex(3)> [1] > %{}
true
iex(4)>

Is Elixir’s nil different than Erlang’s nil or is the documentation wrong?

By nil erlang often refers to the empty list [] - a leftover from the lisp terminology.

More fun.

iex(1)> foo = :""
:""
iex(2)> foo < :nil
true
iex(3)> foo < 2
false

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