簡體   English   中英

如何為新類型定義宏?

[英]How to define Macro for a new Type?

背景

所以,我正在玩弄一個名為“NewType”的概念,我從 F# 和 Scala 等語言中獲得靈感。

我的目標,主要是出於學習目的,是構建一個宏,使創建這種抽象只需要一行代碼。

預期用途

我想創建一個允許我做這樣的事情的宏:

defmodule User do
  require NewType # an absolutely original name for the macro :D

  deftype Name, String.t() # Usage of said macro. Here I am defining a new type called "Name"

  @enforce_keys [:name, :age]
  defstruct [:name, :age]
  @type t :: %__MODULE__{
          name: Name.t,
          age: integer()
        }

  @spec new(Name.t, integer) :: User.t
  def new(name, age), do: %User{name: name, age, age}  
end

現在,這是我創建User的方法:

defmodule Test do
  alias User
  import User.Name

  @spec run :: User.t
  def run do
    name = Name("John")
    User.new(name, 25)
  end
end

如何實現這個接口?

該界面可能會讓您想起Record界面。 那是因為我認為它的 API 有一些我想探索的好主意。

因此,作為起點,我嘗試閱讀 Record 的源代碼,但我並不能真正將其提取出來並使用它來為我的用例創建一個實現,主要是因為我不需要/不想與 Erlang 交互記錄在所有。

所以,一個實現的可能性是,在幕后,把它變成一個元組:

defmodule NewType do
  defmacro new(name, val) do
    quote do
      NewType.to_tuple(unquote(name), unquote(val))
    end
  end

  def to_tuple(name, val), do: {String.to_atom(name), val}
end

然而,這與我想要創建的界面相去甚遠......

問題

  1. 使用 Elixir 宏,是否可以創建我想要的 API?
  2. 我怎樣才能改變我的代碼來實現類似Name("John")的東西?

我的答案

在 Elixir 中閱讀了更多關於宏的信息、與社區交談並閱讀了NewType之后,我完善了我的想法。 雖然無法准確實現我最初的想法,但通過一些更改,您仍然可以獲得NewType的核心優勢。

改變最初的想法

  • 不使用Name("John")語法。 如本文所述, 語法在 Elixir 中無效
  • 沒有defguard 因為類型是@opaque ,所以不可能有一個守衛來分析數據的內部結構而不會引起透析器的抱怨。 由於這里的主要目標是讓 Dialyzer 幫助我檢測問題,並且由於不透明數據的內部結構只能由屬於模塊本身的函數來分析,這意味着這個想法是不可能的。
  • 調用new時不驗證數據類型。 最初我想有一些驗證機制,但這不是必需的,因為如果用戶使用不正確的參數調用new ,dialyzer 會讓用戶知道。
  • 沒有自生成的函數。 而不是有Age.age? Name.name? 我選擇了更通用的NewType.is_type?/2 ,它將完成相同的操作並且更通用。

代碼

考慮到這些變化,這是我想出的宏:

defmodule NewType do
  defmacro deftype(name, type) do
    quote do
      defmodule unquote(name) do
        @opaque t :: {unquote(name), unquote(type)}

        @spec new(value :: unquote(type)) :: t
        def new(value), do: {unquote(name), value}

        @spec extract(new_type :: t) :: unquote(type)
        def extract({unquote(name), value}), do: value
      end
    end
  end

  @spec is_type?(data :: {atom, any}, new_type :: atom) :: boolean
  def is_type?({type, _data}, new_type) when type == new_type, do: true
  def is_type?(_data, _new_type), do: false
end

可以像這樣使用:

類型.ex

defmodule Type do
  import NewType

  deftype Name, String.t()
end

測試.ex

defmodule Test do
  alias Type.Name

  @spec print(Name.t()) :: binary
  def print(name), do: Name.extract(name)

  def run do
    arg = 1
    name = Name.new(arg) # dialyzer detects error !
    {:ok, name}
  end
end

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM