虽然我是 Elixir 的忠实粉丝,但缺乏静态类型一直是我最大的烦恼(也是我认为Gleam如此酷的原因)。我认为静态类型有助于更早地以自动化的方式捕获错误,从而减少有错误的软件,并从长远来看节省时间。
令我非常高兴的是,Elixir 正在开发一种新的类型系统,它有望为我们提供我一直渴望的早期类型检查错误。该系统自 v1.17 以来已逐步推出,当我迁移到v1.18时,我发现了我想要强调的第一个类型检查警告。
与结构体的比较
这是带有相应警告的违规代码:
def get_surrounding_events_as_dt ( events , now = % DateTime { } ) do
time = DateTime . to_time ( now )
next_i = Enum . find_index ( events , fn { _ , event_time } -> time < event_time end ) | | 0
warning: comparison with structs found:
time < event_time
given types:
dynamic(%Time{}) < dynamic()
where "event_time" was given the type:
# type: dynamic()
# from: lib/haex/sun.ex
{_, event_time}
where "time" was given the type:
# type: dynamic(%Time{})
# from: lib/haex/sun.ex:88:10
time = DateTime.to_time(now)
Comparison operators (>, <, >=, <=, min, and max) perform structural and not semantic comparison.
Comparing with a struct won't give meaningful results.
Structs that can be compared typically define a compare/2 function within their modules that
can be used for semantic comparison.
typing violation found at:
│
│ next_i = Enum.find_index(events, fn {_, event_time} -> time < event_time end) || 0
│ ~
(类型检查器尚无法将event_time
解析为Time
结构,在上面的文本中将其保留为dynamic()
。)
这里的问题是<
没有为Time
结构重载(就像在 Rust 中那样),而是执行结构比较。
你应该使用Time . before?
而不是<
(以及DateTime . before
DateTime
等)。
对于Time
来说,这似乎不是问题,因为该结构恰好以与Time . before?
,此测试验证:
test " check_times " do
times =
Enum . zip ( [ 0 .. 23 , 0 .. 59 , 0 .. 59 ] )
|> Enum . map ( fn { h , m , s } -> Time . new! ( h , m , s ) end )
for a <- times do
for b <- times do
assert a < b == Time . before? ( a , b )
end
end
end
DateTime
的情况并非如此,它确实导致了我的家庭自动化系统中的生产错误,我的配偶抱怨过……
当您考虑类型时请记住这一点:类型检查可以保存关系。
我对未来的希望
我一直不喜欢在 Elixir 中匹配原子,因为它很容易犯错误,例如这样:
case Supervisor . start_child ( supervisor , child_spec ) do
{ : error , { : already_stated , pid } } ->
Logger . info ( " Got pid: #{ inspect ( pid ) } " )
( : already_stated
中缺少r
。)
目前这不会产生错误,但我真的希望我们能早日达到这一点,因为我总是犯这类错误。我想我通过测试发现了其中的大部分,但我确信有些会漏掉。
我希望这不会太遥远,因为 v1.18 类型检查器能够捕获如下更简单的情况:
def num_to_descr ( num ) do
case num do
1 -> : one
2 -> : two
_ -> : many
end
end
def print ( num ) do
case num_to_descr ( num ) do
: zero -> IO . puts ( " zero " )
x -> IO . puts ( " Other: #{ x } " )
end
end
warning: the following clause will never match:
:zero
because it attempts to match on the result of:
num_to_descr(num)
which has type:
dynamic(:many or :one or :two)
typing violation found at:
│
41 │ :zero -> IO.puts("zero")
│ ~~~~~~~~~~~~~~~~~~~~~~~~
原文: https://www.jonashietala.se/blog/2024/12/30/type_checking_in_elixir_118