[英]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
然而,這與我想要創建的界面相去甚遠......
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.