Tipos de tiempo de ejecución: más profundo en la madriguera del conejo

Cuando comencé a escribir la nota " Tipos donde no se esperaban ", me pareció que había dominado para llevar los tipos de Erlang al tiempo de ejecución y ahora puedo usarlos en el código del cliente en el elixir. Jaja, que ingenua fui.



Todo lo que sugiera el enlace funcionará para definiciones de tipo de lugar de uso explícitas como use Foo, var: type(). Desafortunadamente, este enfoque está condenado al fracaso si queremos definir tipos en otro lugar: junto a él en el código usando atributos de módulo, o, allí, en config. Por ejemplo, para definir una estructura, podríamos querer escribir algo como esto:



# @fields [foo: 42]
# defstruct @fields

@definition var: atom()
use Foo, @definition


Faro en la Cataluña francesa



El código anterior no es que no manejará el tipo de la manera que queremos; no recopilará en absoluto porque @definition var: atom()arrojará una excepción ** (CompileError) undefined function atom/0.



Enfoque ingenuo



 — « » ( @tsilb, , .) , , , , — .



, , __using__/1: , ( field → type()),  — , , , {Module, :type, [params]}. ~q||, , , , AST. quote/1 : foo: ~q|atom()|. , , . . , - , , , , . , , - - - , .



. , erlang  —  , , .  — , , ( ).



, . , , , , , , —  . , .



Tyyppi



,  — , XY . , , —   — . Tyyppi.



Code.Typespec, . : . , , , . , , .  —  Tyyppi.of?/2, ,  — «»/«» , .



iex|tyyppi|1  Tyyppi.of? GenServer.on_start(), {:ok, self()}
#⇒ true
iex|tyyppi|2  Tyyppi.of? GenServer.on_start(), :ok
#⇒ false


- , Tyyppi.T. Tyyppi.of?/2 - — Tyyppi.of_type?/2.



iex|tyyppi|3  type = Tyyppi.parse(GenServer.on_start)
iex|tyyppi|4  Tyyppi.of_type? type, {:ok, self()}
#⇒ true


, , , , , . :erlang.term_to_binary/1, Config.Provider.





, : . , . , key: type(). Access, upserts. , Ecto.Changeset cast_field/1 validate/1.



, , , , ( , ).



defmodule MyStruct do
  import Kernel, except: [defstruct: 1]
  import Tyyppi.Struct, only: [defstruct: 1]

  @typedoc "The user type defined before `defstruct/1` declaration"
  @type my_type :: :ok | {:error, term()}

  @defaults foo: :default,
            bar: :erlang.list_to_pid('<0.0.0>'),
            baz: {:error, :reason}
  defstruct foo: atom(), bar: GenServer.on_start(), baz: my_type()

  def cast_foo(atom) when is_atom(atom), do: atom
  def cast_foo(binary) when is_binary(binary),
    do: String.to_atom(binary)

  def validate(%{foo: :default} = my_struct), do: {:ok, my_struct}
  def validate(%{foo: foo} = my_struct), do: {:error, {:foo, foo}
end


No tengo idea de cuál es el valor práctico de esta biblioteca en producción (mentira, lo sé: ninguno), pero ciertamente puede ser de gran ayuda durante el desarrollo, permitiéndole restringir su búsqueda y aislar errores extraños relacionados con la naturaleza dinámica de los tipos en Elixir. especialmente cuando se trata de fuentes externas.



Todo el código de la biblioteca está disponible, como siempre, en github .






¡Feliz escritura en tiempo de ejecución!




All Articles