虽然我是 Elixir 的忠实粉丝,但缺乏静态类型一直是我最大的烦恼(也是我认为Gleam如此酷的原因)。我认为静态类型有助于更早地以自动化的方式捕获错误,从而减少有错误的软件,并从长远来看节省时间。
令我非常高兴的是,Elixir 正在开发一种新的类型系统,它有望为我们提供我一直渴望的早期类型检查错误。该系统自 v1.17 以来已逐步推出,当我迁移到v1.18时,我发现了我想要强调的第一个类型检查警告。
与结构体的比较
这是带有相应警告的违规代码:
def get_surrounding_events_as_dt ( events , now = % DateTime { } ) dotime = 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:10time = 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 thatcan 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 " dotimes =Enum . zip ( [ 0 .. 23 , 0 .. 59 , 0 .. 59 ] )|> Enum . map ( fn { h , m , s } -> Time . new! ( h , m , s ) end ) for a <- times dofor b <- times doassert a < b == Time . before? ( a , b )endendend 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 ) docase num do1 -> : one2 -> : two_ -> : manyendend def print ( num ) docase num_to_descr ( num ) do: zero -> IO . puts ( " zero " )x -> IO . puts ( " Other: #{ x } " )endend
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