【问题标题】:How to create and use a custom Erlang behavior?如何创建和使用自定义 Erlang 行为?
【发布时间】:2015-11-26 23:59:23
【问题描述】:

尝试在 Erlang 中定义自定义行为,我无法找到在行为定义模块中应用回调函数的方法。编译器声称,回调函数未定义。

我希望行为中的回调函数能够像 OO 语言中的抽象方法一样工作,可以在不指定实现的情况下使用该方法。

下面的例子定义了一个回调函数 fn。然后,该函数在 add_one 中使用。 fn 的实际作用是由实现此行为的 Erlang 模块控制的。

-module( mybeh ).

-callback fn( A::number() ) -> B::number().

-export( [add_one/1] ).

add_one( A ) ->
  1+fn( A ).

但是当我尝试编译文件 mybeh.erl 时,我收到以下错误消息:

$ erlc mybeh.erl
mybeh.erl:8: function fn/1 undefined

我在erlangcentral.org、learnyousomeerlang.com 或 metajack.im 上找到的代码示例过于简单,无法涵盖此案例。我也没有运气通过 Github 上著名的 Erlang 项目(虽然可以更努力地尝试)。

【问题讨论】:

    标签: erlang


    【解决方案1】:

    你很亲密。它实际上比您尝试的要容易:

    -module(my_behavior).
    
    -callback fn(A :: term()) -> B :: term().
    

    编译器可以完全理解这一点。

    这么简单,有点虎头蛇尾。

    编辑

    “很酷的故事,怎么用?”

    没有什么比工作示例更能说明问题了:

    这里我们有一个抽象服务。它应该对范围很窄的消息做出回应,并嘲笑其他任何事情。不过,它的特殊元素是它接受一个模块的名称作为其启动参数,该名称定义了其行为的某些特殊方面——这就是回调模块。

    -module(my_abstract).
    -export([start/1]).
    
    start(CallbackMod)->                
        spawn(fun() -> loop(CallbackMod) end).
    
    loop(CBM) ->
        receive
            {Sender, {do_it, A}} ->
                Sender ! CBM:fn(A),
                loop(CBM);
            stop ->
                io:format("~p (~p): Farewell!~n",
                          [self(), ?MODULE]);
            Message ->
                io:format("~p (~p): Received silliness: ~tp~n",
                          [self(), ?MODULE, Message]),
                loop(CBM)
        end.
    

    所以这里我们定义一个非常简单的回调模块,按照上面'my_behavior'定义的行为:

    -module(my_callbacks).
    -behavior(my_behavior).
    -export([fn/1]).
    
    fn(A) -> A + 1.
    

    它在行动!

    1> c(my_behavior).
    {ok,my_behavior}
    2> c(my_abstract).
    {ok,my_abstract}
    3> c(my_callbacks).
    {ok,my_callbacks}
    4> Service = my_abstract:start(my_callbacks).
    <0.50.0>
    5> Service ! {self(), {do_it, 5}}.
    {<0.33.0>,{do_it,5}}
    6> flush().
    Shell got 6
    ok
    7> Service ! {self(), {do_it, 41}}.
    {<0.33.0>,{do_it,41}}
    8> flush().                        
    Shell got 42
    ok
    9> Service ! stop.
    <0.50.0> (my_abstract): Farewell!
    stop
    

    那么行为定义有什么好处呢?它实际上并没有任何事情!那么,所有这些 Dialyzer 类型层次结构声明有什么用?他们也不做任何事情。但是它们会帮助您自动检查您的工作,以确保您不会遇到一些令人兴奋的运行时失败——但 Dialyzer 和行为定义都不会强迫您做任何事情:它们只是警告我们(可能)即将到来的厄运:

    -module(my_other_callbacks).
    -behavior(my_behavior).
    -export([haha_wtf/1]).
    
    haha_wtf(A) -> A - 1.
    

    当我们构建它时,我们得到:

    10> c(my_other_callbacks).
    my_other_callbacks.erl:2: Warning: undefined callback function fn/1 (behaviour 'my_behavior')
    {ok,my_other_callbacks}
    

    但请注意,这个模块已经实际编译过并且仍然可以独立使用(但不是由我们的抽象服务,它期望找到在任何称为 my_behavior 的任何东西中定义的 fn/1) :

    11> my_other_callbacks:haha_wtf(5).
    4
    

    希望这个简短的演练能对这条道路有所启发。

    【讨论】:

    • 实际上,我需要让函数 apply_fn(现在是 add_one)工作。我修改了示例以使其更清晰。
    • @JörgenBrandt 你在想名字的时候太费劲了。行为已经在我们编写的第一件事中定义。那是它自己的文件。您必须定义一个抽象服务,该服务期望使用符合此行为的回调模块(即第二个文件)。然后你必须定义真正遵守行为定义的回调模块(这些都是下一个文件)。尝试构建上面的三个示例模块——一旦你自己动手,它会突然变得更有意义。
    • 现在我明白了!行为定义只是为了让您知道要实现哪些功能,因此,如果您遗漏了一个功能,则该事物会在编译时崩溃。感谢所有的努力。
    • 个人观点(如果我错了请纠正我),但我相信可以在行为模块(my_behavior)中定义“抽象接口”。这样,它看起来就不那么“无用”了。我相信gen_server行为是通过gen_server本身实现的抽象接口和循环函数实现的;它还会调用gen 模块。
    • @eazar001 你是完全正确的——这是很正常的事情。每当有一个模块定义基本行为的操作时,这更容易理解。有时我会遇到这样的情况,即行为实际上是一个公共界面,并且有多个后端前端(例如,游戏服务器中的生物控制器 AI),然后有一个单独的仅行为声明。在这里,我只想说明回调声明的语法——没有理由将它放在单独的模块中。
    猜你喜欢
    • 2023-03-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-08-14
    • 1970-01-01
    • 2022-11-17
    相关资源
    最近更新 更多