Erlang 中为什么我们需要模块?这是一个比较发散的想法,我已经思考了一段时间了。我在这里提出一种稍微不同的编程方式:

基本思想是:

以下按随机顺序讨论:

为什么 Erlang 有模块?模块有好的一面也有不好的一面:

好:提供了一个编译单元、一个代码分发单元、一个代码替换单元。

坏:很难决定把一个单独的函数放在哪个模块里。破坏了封装(稍后详述)。

题外话:lib_misc.erl

当我在编程时,我经常会想到应该在 lists.erl 中有一个 foo/2 函数,但它就是没有。应该有,但就是没有——foo/2 是一个小而自包含的东西。为什么它应该在 lists.erl 中?因为它 "感觉对了"。

字符串是列表,所以为什么我们有两个模块 lists.erlstring.erl?我应该如何决定我的新字符串/列表处理函数应该放在哪个模块中?

为了避免所有精神上的痛苦,当我需要一个应该在其他地方但又不在的小函数时,我就把它放在一个名为 elib1_misc.erl 的模块中。

我的 elib1_misc 导出了以下内容:

added_files/2        make_challenge/0
as_bits/1            make_response/2
as_bits_test/0       make_response_test/0
bdump/2              make_test_strings/1
bin2hex/1            make_test_strings_test/0
bin2hex_test/0       make_tmp_filename/2
check_io_list/1      merge_kv/1
collect_atom/1       merge_kv_test/0
collect_atom_test/0  mini_shell/0
collect_int/1        module_info/0
collect_int_test/0   module_info/1
collect_string/1     ndots/1
collect_string_test/0 nibble_to_hex_char/1
collect_word/1       nibble_to_hex_char_test/0
complete/2           odd/1
complete_test/0      on_exit/2
dos2unix/1           out_of_date/2
downcase_char/1      outfile/2
dump/2               padd/2
dump_tmp/2           perms/1
duplicates/1         perms_test/0
ensure_started/2      pmap/2
eval_file/1          pmap1/2
eval_file_test/0     pmap1_test/0
eval_string/1        pmap_test/0
eval_string_test/0   priority_receive/0
every/3              random_seed/0
expand_env_vars/1    random_string/1
expand_file_template/3 random_string/2
expand_string_template/2 read_at_most_n_lines/2
expand_tabs/1        read_at_most_n_lines_test/0
expand_tabs_test/0   remove_duplicates/1
expand_template/2    remove_duplicates_test/0
extract_attribute/2  remove_leading_and_trailing_whitespace/1
extract_attribute_test/0 remove_leading_and_trailing_whitespace_test/0
extract_prefix/2      remove_leading_whitespace/1
fetch/2              remove_prefix/2
fetch_test/0         remove_prefix_test/0
file2lines/1         remove_trailing_whitespace/1
file2lines_test/0    replace/3
file2md5/1           root_dir/0
file2numberedlines/1   rpc/2
file2numberedlines_test/0 safe/1
file2paras/1         show_loaded/1
file2stream/1        signed_byte_to_hex_string/1
file2string/1        signed_byte_to_hex_string_test/0
file2template/1      skip_blanks/1
file2term/1          skip_blanks_test/0
file_size_and_type/1 skip_to_nl/1
find_src/1           skip_to_nl_test/0
first/1              sleep/1
flatten_io_list/1    spawn_monitor/3
flush_buffer/0       split_at_char/2
for/3                split_at_char_test/0
force/1              split_list/2
foreach_chunk_in_file/3 split_list_test/0
foreach_word_in_file/2 string2exprs/1
foreach_word_in_string/2 string2exprs_test/0
forever/0            string2html/1
get_erl_section/2    string2latex/1
get_line/1           string2lines/1
get_line/2           string2lines_test/0
have_common_prefix/1 string2stream/1
have_common_prefix_test/0 string2stream_test/0
hex2bin/1            string2template/1
hex2bin_test/0       string2template_test/0
hex2list/1           string2term/1
hex2list_test/0      string2term_test/0
hex_nibble2int/1     string2toks/1
hex_nibble2int_test/0 string2toks_test/0
id/1                 sub_binary/3
include_dir/0        template2file/3
include_file/1       term2file/2
interleave/2         term2string/1
is_alphanum/1        test/0
is_blank_line/1      test1_test/0
is_prefix/2          test_function_over_substrings/2
is_prefix_test/0     tex2pdf/1
is_response_correct/3 time_fun/2
keep_alive/2         time_stamp/0
lines2para/1         to_lower/1
list2frequency_distribution/1 to_lower_test/0
list2frequency_distribution_tetrim/1
longest_common_prefix/1 trim_test/0
longest_common_prefix_test/0 unconsult/2
lookup/2            unsigned_byte_to_hex_string/1
lorem/1             unsigned_byte_to_hex_string_test/0
ls/1                which/1
which_added/1

现在我发现这非常方便,当我编写一个新的小型实用程序函数时,我就把它放在 elib1_misc.erl 中——无需在选择模块名称上进行精神上的痛苦。

我发现这种非常方便的观察告诉我一些关于模块的信息——我喜欢我的 elib1_misc,它感觉很对。

(题外话——似乎许多开发项目都有他们自己的私有 lib_misc...)

这引出了我的问题的重点。

我们真的需要模块吗?Erlang 程序由许多小函数组成,模块似乎唯一有用的地方是隐藏 letrec。经典的例子是斐波那契数列。我们想暴露 fib/1,但隐藏辅助函数 fib/3。使用模块,我们说:

-module(math).
-export([fib/1]).

fib(N) -> fib(N, 1, 0).

fib(N, A, B) when N < 2 -> A;
fib(N, A, B) -> fib(N-1, A+B, A).

缺点是我们必须 发明 一个模块名 math——它的 唯一 目的就是隐藏我们不希望被调用的 fib/3 的定义。如果我们在 math 模块中放入第二个函数,那么这个第二个函数就可以调用 fib/3,这就破坏了 fib/3 的封装。

我们可以这样说:

let fib = fun(N) -> fib(N, 1, 0) end
in
    fib(N, A, B) when N < 2 -> A;
    fib(N, A, B) -> fib(N-1, A+B, A).
end.

我几乎不敢为这个建议一种语法,因为我一直在关注这个论坛上的另一个帖子,在那里语法讨论似乎鼓励了很多评论。** 请在这里建议替代语法,但不要评论其他人的建议...

我只想讨论我们为什么要有模块。

另一个问题:

模块的想法是否来自于函数必须存储在某个地方的想法,所以我们将它们存储在一个文件中,然后我们将该文件(作为一个单元)吸入系统中,所以该文件就变成了一个模块?

如果所有文件都单独存储在一个数据库中,这会改变事情吗?我越来越觉得,如果所有函数都在一个具有唯一名称的 key_value 数据库中,那就太好了。

lookup(foo,2) 将从数据库中获取 foo/2 的定义 foo

唯一名称位很有趣——这是一个好主意吗?限定名称(即像 xxx:foo/2)或 (a.b.c.foo/2) 这样的名称听起来是个好主意,但是当我编程时,我必须发明 xxxa.b.c,这非常困难。它还涉及 "决策问题",如果命名空间 xxxa.b.c 已经存在,我必须 选择 将我的新函数放在哪个命名空间中。

我认为这里可能存在别名的情况。在开发时可以使用 joe:foo/2,"joe" 将扩展为一个可怕的随机本地字符串,真实名称为 ab123aZwerasch123123_foo/2,但在我选择一个合理的名称之前,我将无法发布我的代码或使其可供第三方使用。

((管理命名空间似乎非常棘手,很多人似乎认为通过在名称中添加 "." 就可以解决这个问题,但是管理一个包含 foo.bar.baz.z 这样的名称的命名空间与管理一个包含 foo_bar_baz_z 这样的名称或包含 0x3af312a78a3f1ae123 这样的名称的命名空间一样复杂——问题是我们必须从一个像 www.a.b 这样的符号名称到一个像 123.45.23.12 这样的引用——但是我们如何发现初始名称 www.a.b?——有两个答案——a) 我们被赋予了这个名称(即我们点击了一个链接)——我们不知道这个名称,但我们搜索它))

当程序很小时,我们可以容忍 "只有代码" 在 "几个模块中",代码与元数据的比率很高。当程序很大时,我们需要大量的元数据来理解它们。

我希望看到所有具有所有元数据的函数都在一个数据库中。

我想说:lookup(foo,2,Attribute),其中 Attribute = code|source|documentation|type signatures|revision history|authors|...

我想得越多,就越觉得程序开发应该被视为改变一个键值数据库的状态。所以我设想:

  1. 所有函数都有唯一名称
  2. 没有模块
  3. 我们通过搜索数据库中描述函数的元数据来发现函数的名称
  4. 所有公共函数(想想开源)都在同一个数据库中

我们可以创建一个系统来做到这一点。

我认为这将使开源项目更容易,因为贡献的粒度会降低。你可以贡献一个单独的函数——而不是整个应用程序。

(( GUT 风格的开源项目的一个问题是,没有一个函数数据库,我经常想要这个项目中的一个函数,另一个项目中的另一个函数——可重用部分的粒度应该是单个函数。函数真的很容易重用,模块更难重用,整个应用程序非常难以重用(除非通过通信通道隔离))

可能的扩展。

  1. 投票晋升
  2. 审查流程

给定一个包含 所有 函数的原始数据库,我们可以派生出一个 "已批准" 函数数据库。

流行的函数可以移动到已批准的数据库中——审查过程需要讨论——所以有点像同行评审/wiki 的东西。

评论?

志愿者?

/Joe

Hi, 据我所知,在运行环境中替换代码的能力取决于模块。

所以模块可以防止停机并允许 7/24 运行。

cheers, Ralf

Amy Lear 我认为在运行环境中替换代码的能力取决于模块。 所以模块可以防止停机并允许 7/24 运行。 cheers, Ralf 这只是因为这是我们拥有的实现。 VM 维护特定函数的几个版本的能力仍然可以在该粒度级别上维护......前提是有办法以原子方式确保没有任何东西调用新版本的函数,而所有相关函数的相关更改都同时滚动到 VM 中,因为如果您发现自己重构几个函数以添加新功能,您可能会移动很多东西。 这确实更容易使用模块作为完整的依赖单元。 是否有一种干净的语义方式来跟踪这种代码数据库中的跨函数依赖关系(可能通过某种类型的版本标签)?

马赞·哈拉克 难道你只是在重新定义问题吗? 你的下一个问题将是命名更多通用类型的函数。 想象一下你有 3 个不同的搜索函数,它们在不同的模块中做不同的事情。 你的解决方案将取代: foo:search(), bar:search(), baz:search() 给出 foo_search(), bar_search(), baz_search() 现在你想添加一个函数来做替换; 但是替换什么? 假设你调用它 replace() 现在我想做一个,因为它专门针对我的问题,所以我有一个决策问题 foo_replace() ? bar_replace() ? 所以我将最终定义一个前缀,以便我的函数版本(当它们与已存在的版本冲突时)始终是唯一的,因此: mazen_replace() 现在每个人都开始创建带有前缀的函数,现在他们想开始扩展其他函数,因此他们在那些之前构建一个前缀: mazen_foo_replace() 在你意识到之前,你会有 20 个以 "search()" 结尾的函数,其中 search 意味着前缀暗示的任何内容...... 所以你回到了平方 -1,因为现在情况更糟,你没有模块,也没有应用程序,因为应用程序会变成之前的模块。 事实上,这已经是一个问题,我所有的开源应用程序(以及非开源应用程序)都在模块上有一个前缀,因为我不能把我所有的服务器类型模块都称为 "服务器"(没有命名空间)。 我 2 美分。 /M

Dmitrii Dimandt

So I imagine:

  1. all functions have unique names
  2. there are no modules
  3. we discover the name of a function by searching metadata describing the function in a database
  4. all public functions (think open source) are in the same database

We could make a system to do this.

Hm... you'd still need some kind of modules.

For instance, in Webmachine each resource is expected to have some, or all, of predefined public/exported functions: to_html, resource_exists, allowed_methods etc.

So, each resource will have functions with the same name. If we move all functions to a global namespace, then how do we differentiate between these functions? Probably by naming them resource_name_function_name?

Furthermore, what do we do with behaviours? A behaviour is encapsulated within a module, also with a predefined set of exported funcions (and any number of custom exported functions).

戈登·格思里 模块有两个优点:

http://erldocs.com/R14B02/stdlib/digraph.html?i=2&search=digra#get_digraph/1

我可以利用我知道的几个暴露的函数,确信如果需要,我可以回来并掌握剩余的接口。 我有信心那里有一种稳健性,使使用它的代码可以维护。 (我对可维护性的理解是能够忽略大部分东西,大部分时间......)。 我可以在这个更高层次的抽象中了解代码。

我们也有一个模块,其中包含大量随机函数,嗯,我们有三个 (hn_util, util, util2),它们都只是库函数。 在实用程序模块中,导出与未导出函数的比率可能是 7 比 1。

在功能模块中,导出/未导出的比例从 1-1(在定义 api 的模块中)到 1-3 不等。

如果我们有(承诺的)-export-to() 指令,纯导出 fn 的数量将会下降。

鉴于我们围绕 -export() 构建我们的测试策略,这种分离至关重要...

戈登

...还有...

紧随德米特里之后,我在写邮件时说,我们将单元测试挂载到模块上,并以这种抽象级别进行设计

Kresten Krab Thorup 模块有很多用途,但它们经常被混淆。 昨天我画了这张图:

Vlad Dumitrescu 在私有于模块的辅助函数中,如果从许多地方调用此辅助函数,则 letrec 可能不是一个很好的替代。 我不想每次需要它时都重复它的定义......

我仍然觉得除非我们改变对程序的看法,否则问题仍然存在于前缀级别。 如果在范式中要发生重大转变,那么问题是结果是否仍然是 Erlang...

Jesper Louis Andersen 这是一个伟大的想法。 为什么? 因为它是存在类型的方式 :-) 请注意,在 Erlang 中,模块是一个值。 可以调用 Mod:Fun(..)。 其次,请注意参数化模块扩展允许我们跨某个其他模块参数化模块。 具体来说,我们可以有 -module(RBTree, [OrderMod]). 其中 OrderMod 是一个具有以下功能的模块 -opaque t() :: ..... -export_type([t/0]). -spec order(t(), t()) -> lt | eq | gt. 因此,我们已经概括了我们的 RedBlack 树,以便能够使用任何排序关系,而不仅仅是内置的关系。 Gilad 所倡导的风格是来自标准 ML 社区的 "完全函子风格"。 SML 术语中的 Functor[1] 是从模块到模块的函数("functor" 源自范畴论,它指定了一级 "函数"[2])。 在这种风格中,您通过将较小的模块化部分拼接在一起来构建程序,从而在过程中构建越来越大的模块。 示例:gen_server 是从一个模块(服从 gen_server 行为并具有 handle_* 函数)到一个服务器的特定实例的函子。 它还为您提供了通往 OOP 概念的道路,正如可以从参数化模块扩展及其在各个地方的使用中观察到的那样。 在具有静态类型的语言中,您当然可以强制执行函子应用程序是类型正确的。 如果您允许模块作为此类语言中的值,您将获得存在类型,这是一种将类似 OOP 的东西引入这些语言的方法[3]。 所以经过一小段旅行:Erlang 已经拥有这个:P 模块是构建大型系统的重要组成部分。 您可以将函数打包在一个模块中并将其作为单元运输的这一概念非常重要。 还有您可以一次更换模块组件的概念。 与一个大型全局命名空间相比,我感觉将函数放置在正确模块中的问题并不那么严重。 它不仅仅是 "将一堆函数扔在一起"。 具体来说,模块允许我拥有数据结构的 抽象 视图。 在上面,我不知道 t() 是什么。 我只允许使用 OrderMod:compare(T1, T2) 比较两个 t()。 也就是说,我唯一允许做的代数运算就是这个。 程序的其他部分可能对 t() 有不同的看法,并且能够用它做更多的事情,但对我来说,它是一个不透明的术语。 它很重要,因为有人可以去替换 t(),而我的代码根本不必更改。 如果 t() 泄露,这是不可能的。 我很感激 dialyzer 可以找到泄漏的地方。 同样,一个模块可以隐藏它是使用 gen_server 实现的事实。 所以我可以去用 gen_fsm 替换它,并且没有人必须更改他们的代码,因为我导出的 API 正是我将保持稳定的 API。 进程组件化堆。 模块组件化代码。 [1] 请注意,在编程中使用的词 functor 与计算机科学中的特定事物相关。 该含义在标准 ML、Haskell 和 C++ 中有所不同。 [2] 我在撒谎。 基本概念是态射,其中函数是考虑集合范畴时的特例。 [3] 我仍然不明白为什么你不能在 Erlang 中使用静态类型。 保守的方法是简单地对所有消息传递使用多态变体,并且基本上它可以立即工作。

Tim Carpenter 大家好, 我坚持要求不对其他解决方案发表评论,而是提出我自己的想法... 1. 有些代码单元只有作为一个集合才有意义。 谁会单独使用 gen_server 的一部分? 嗯,你可以,但将它分开会使它暴露于可能扭曲该单元的重点目的的修改。 2. 有些函数具有有意隐藏的方面、私有调用等,并且这些最好保持隐藏或被视为一个逻辑整体,否则可能会发生副作用或膨胀。 3. 我看不出我们为什么不能将函数在逻辑上分组到模块中,而不是将代码放在那里...... 即模块包含组成它的函数引用。 这允许对函数组进行逻辑处理以进行代码管理,并且允许一个函数出现在多个模块中而无需代码重复。 它还允许您添加/删除/替换模块中的函数,而无需知道其他函数的源代码已被触及! 4. 命名约定应该给予高度重视,以实现简单性和直观性。 函数可能在 n 个集合中,因此严格的层次结构可能不适合。 5. 如果函数被命名为位于模块中,则可能需要对全名提供抽象,因此可以使用 modulename.sort/3,并且模块定义会将其转换为可以随时更改的唯一的长名。 程序员还应该能够随意使用长名,将模块用作一种方便的批量导入/编译工具,它会在编译时中断(希望如此)。 某种形式的 'expects...' 表示法可以允许某种形式的保护,以防止在共享模块中无意中交换函数而改变行为。 6. 我们可能需要 3 个函数命名元素:简写、长名和版本,因此可以像使用长名函数那样使用最新版本,锁定到长名函数的特定版本或使用简写最新版本或其他此类组合。 7. n 层模块 - 模块的模块? 有可能。

Bengt Kleberg 问候, 如果能够进行模块重命名/命名空间划分,那将会非常好,这将允许我执行以下操作: 来自开发人员 A 的模块 "misc_lib" 和 "misc_lib_helper" 应该能够使用其原始名称相互调用。 来自开发人员 B 的模块 "misc_lib" 和 "misc_lib_helper" 应该能够使用其原始名称相互调用。 我应该能够同时使用我创建/重命名的名称调用所有 4 个。 因此,我需要将模块加载为集合/应用程序/???,这会在该集合之外重命名它们(可以向所有模块添加相同的前缀),并且仍然允许集合/应用程序/??? 中的文件继续使用旧名称。 在改进 stdlibs 方面,我建议我们创建新的、更好的模块。 保留旧模块(永远?),但在手册页上打印一个大的 "已弃用"。

加里·海 我正在研究面向状态的编程模型,我和你一样有同样的问题。 在 SOP 的观点中,Erlang 进程是一个 FSM,适合作为状态的函数粒度。 FSM 的有限状态集就像 Erlang 模块一样,但它总是跨越模块边界。 因此,在 SOP 模型中,模块概念非常模糊,仅用于函数唯一不同的名称。 状态活动的模型是一个函数,如下所示: act(Entity, Input) -> {ok, Output} | {ok, Output, UpdatedEntity} | {error, Error} | {stop, Output, Entity} 状态的输出可以是 {NextState, Output}。 NextState 可能是下一个状态活动函数,或者只是下一个状态的指令。 然后,FSM 引擎将指令匹配到状态活动,然后将先前的状态以先前的输出作为输入传递到新状态。 状态活动函数可能来自任何地方,模块中的函数,从数据库加载的二进制文件,由 JIT 动态生成的代码。 --------加里·海

杰斯珀·路易斯·安德森 我认为我们需要模块,但这篇文章引发了我一段时间以来的一个想法。 我想拥有模块重命名/命名空间! 具体来说,我想要一种机制,通过它可以清理 stdlib 中的命名混乱,而不影响旧程序。 也就是说,我想能够在应用程序/模块级别,以某种方式重命名函数调用,以便可以清理参数顺序等等。 我不在乎解决方案是什么。 我追求的目标是:清理 stdlib 的混乱。 您可能会争辩说这是一件混乱的事情,但我认为如果您想继续使用标准库,这是必须的: * 我们不能更改现有函数,因为人们依赖它们。 * 现有函数不一致。 这是我想打破的社会僵局。 事实上,我认为只有当我们允许多个具有相同名称(但源于不同命名空间)的模块共存时,我们才能打破僵局。 我不想要嵌套。 只有两个级别,所以我可以这样说 -use_namespace(v2_modules)。 在使用版本 2 的 stdlib 的模块中,其中事情被明智地命名。 在某个时候,您可以翻转过来并使用兼容性层编译旧代码。

蒂姆·沃森 大家好, 我坚持要求不对其他解决方案发表评论,而是提出我自己的想法... 1. 有些代码单元只有作为一个集合才有意义。 谁会单独使用 gen_server 的一部分? 嗯,你可以,但将它分开会使它暴露于可能扭曲该单元的重点目的的修改。 2. 有些函数具有有意隐藏的方面、私有调用等,并且这些最好保持隐藏或被视为一个逻辑整体,否则可能会发生副作用或膨胀。 3. 我看不出我们为什么不能将函数在逻辑上分组到模块中,而不是将代码放在那里...... 即模块包含组成它的函数引用。 这允许对函数组进行逻辑处理以进行代码管理,并且允许一个函数出现在多个模块中而无需代码重复。 它还允许您添加/删除/替换模块中的函数,而无需知道其他函数的源代码已被触及! 4. 命名约定应该给予高度重视,以实现简单性和直观性。 函数可能在 n 个集合中,因此严格的层次结构可能不适合。 5. 如果函数被命名为位于模块中,则可能需要对全名提供抽象,因此可以使用 modulename.sort/3,并且模块定义会将其转换为可以随时更改的唯一的长名。 程序员还应该能够随意使用长名,将模块用作一种方便的批量导入/编译工具,它会在编译时中断(希望如此)。 某种形式的 'expects...' 表示法可以允许某种形式的保护,以防止在共享模块中无意中交换函数而改变行为。 6. 我们可能需要 3 个函数命名元素:简写、长名和版本,因此可以像使用长名函数那样使用最新版本,锁定到长名函数的特定版本或使用简写最新版本或其他此类组合。 7. n 层模块 - 模块的模块? 有可能。

Masklinn 据我所知,除非通过 "__" 前缀明确要求,否则 Python 永远不会 "搞乱名称",这很少使用,并且通常只用于避免继承情况下的命名冲突。 除了这个非常明确的实例之外,您是否有 Python 执行任何名称修饰的实例?

Joe Armstrong 如果我们认为模块是函数的容器,那么模块就是一个 "函数集"——我的问题在于粒度。 最小的重用单位是模块。 假设我的应用程序只需要某个特定模块中的一个函数——我被迫加载整个模块。 这妨碍了重用——我 经常 阅读库代码并找到一个单独的函数并将其剪切并粘贴到我的新代码中。 它也阻碍了共享——因为我可以共享的最小单位是一个模块。 分发单元是一个应用程序——一组模块——为什么这不能是函数列表我不明白。 我认为我希望将应用程序视为提供基于消息的服务的东西,该服务在内部由一组函数而不是一组模块构建。 /Joe

乔·阿姆斯特朗 我同意 - 我仍然喜欢好的 'ol Knuthian 方式 - 从源文件开始,然后添加补丁文件。 你可以创建一个语言来为这个创建一个语言构造。 new_foo = alias foo/2

蒂姆·沃森 我认为不摆脱模块是个好计划。 拥有全局(混乱的)名称是通往痛苦的道路。 在搞乱名称的语言(python、C++ 等)中,它会造成一场血腥的噩梦。 我会避开这个。 我喜欢拥有一个全局代码数据库的想法,但不是所有代码都漂浮在某个虚无缥缈的顶级命名空间中。 http://www.haskell.org/hoogle/http://hackage.haskell.org/packages/hackage.html 中最酷的东西是良好的标准化构建工具和托管存储库的结合,因此我更倾向于专注于改进(和扩展)诸如 rebar 和 agner/erlware 之类的东西,以提供相同的开发人员体验,而无需过多地更改语言。 引入 letrec 是一个非常棒的建议。 此外,使进行部分应用程序和函数组合更容易将简化许多重用案例,而不会破坏模块系统。 我不会太远地偏离替代解决方案的领域,但鉴于 OTP 团队阅读此列表,我不希望错过提到模块系统的一些小问题,这些问题可能需要进行彻底检查的机会。 - 完全支持参数化模块 不是因为我想将它们用作状态包,而是因为它们使我可以轻松地实现类似于函子的东西,而无需驻留在进程中。 这意味着所有工具都需要支持这个概念,并且它有文档记录等等。 扩展模块系统,以便您可以 -extend 模块以创建新的具体实现将很有用: %% finder.erl -module(finder). -functor([M]). -compile(export_all). find() -> walk(fun M:match/3). %% rx_finder.erl -module(rx_finder). -extends(finder, [re]). 也许那里的语法/方法不太正确,但我要表达的观点是,通过参数化模块和 -extends,您应该能够轻松地组合东西。 - 更好地支持 import 随着您处理的代码数量的增加,您需要能够以更简单和更直观的方式导入离散的代码单元。 我不太喜欢 Java,但您可以看到在那个世界中,任何项目中使用的大量库(通常是开源库)意味着您根本无法避免不得不导入东西(包、类、静态方法/函数)而不发疯。 尽管 Erlang 仍然(谢天谢地)足够简单,没有这个问题,但我怀疑随着项目数量的蓬勃发展,它最终会接近临界质量。 此外,能够支持诸如 -import_alias(name, mod) 之类的东西会非常方便,尽管我意识到这使用 parse_transform 非常简单。 - 完全支持包 就我个人而言,我认为我们大多数人遵循的 convention 很好,但它确实变得相当乏味。 包、命名空间或嵌套模块都是已知有效并且被其他语言广泛采用的解决方案。 Erlang 中的包系统工作正常,但并非所有工具今天都正确地支持它(cover、reltool 和一些其他工具已损坏)。

Joe Armstrong 如果我们认为模块是函数的容器,那么模块就是一个 "函数集"——我的问题在于粒度。 最小的重用单位是模块。 假设我的应用程序只需要某个特定模块中的一个函数——我被迫加载整个模块。 这妨碍了重用——我 经常 阅读库代码并找到一个单独的函数并将其剪切并粘贴到我的新代码中。 它也阻碍了共享——因为我可以共享的最小单位是一个模块。 分发单元是一个应用程序——一组模块——为什么这不能是函数列表我不明白。 我认为我希望将应用程序视为提供基于消息的服务的东西,该服务在内部由一组函数而不是一组模块构建。 /Joe

Joe Armstrong 我同意 - 我仍然喜欢好的 'ol Knuthian 方式 - 从源文件开始,然后添加补丁文件。 你可以创建一个语言来为这个创建一个语言构造。 new_foo = alias foo/2

Joe Armstrong 这取决于 -import 的含义。 -import(foo, [bar/2 ]). 有两种解释 a) "当您 运行 此程序并找到对 foo:bar/2 的调用时,请去看看是否可以找到 foo 的代码,加载它然后调用函数 bar/2" b ) "当您 编译 此程序时,找到 foo:bar/2 的代码并提取 bar/2 的代码并将其包含在我的代码中" - 在这种情况下,您在编译时解析名称。 我猜在开发时 a) 很有用,但当您部署代码时,它应该是 b) /Joe

蒂姆·沃森 我现在发现这非常方便,当我编写一个新的小型实用程序函数时 我将其粘贴在 elib1_misc.erl 中 - 没有涉及选择模块名称的精神痛苦。 但这对其他人来说并不是很有帮助,他们将不得不精神上解析您的 lib_misc 库以找出他们在寻找什么。 这听起来有点像垃圾场。 >> 我发现这种非常方便的观察告诉我一些关于模块的信息 - 我喜欢我的 elib1_misc,它感觉很对。>> (旁注 - 似乎许多开发项目都有自己的私有 lib_miscs ...) 如果将这些库分解为组成部分,然后将这些部分分解为组成项目,它们可能会更容易重用。 我在许多项目中都看到了同样的事情 - 一个 包装一个 gen_server 的 ,其中可能包含一些对 application:get_env 的调用,以及一个 等等。 我早些时候在 github 上写了一个 erl-config 包,直到我意识到 gproc 对我的需求来说会是更好的选择。>> 这引出了我的问题的重点。>> 我们真的需要模块吗? Erlang 程序由许多小 函数,模块似乎唯一有用的地方是隐藏 letrec。 我认为这是一种过度简化。 编译单元(例如,将代码放在不同的文件中)首先也是最重要的是对人类有益的。 我认为根据主题领域和/或它们相关的过程对函数进行分类非常有用。 一旦你进行了分类,将所有类别放在一个地方是非常明智的。 OCaml 和 Haskell 程序员(我只有前者的经验)大量使用 letrec(以及类似的概念,例如 Haskell 的 where),但不会梦想着废除我们的模块系统,仅仅因为我们可以以这种方式封装东西。 模块是一种有用的分类,可以让人脑记住东西。 像包一样,它们帮助我们记住在哪里寻找东西。 它们对于隐藏也很有用 - 破坏封装是一种选择,而不是你在设计模块时无法帮助的事情。 缺点是我们必须 发明 一个模块名 math - 其 唯一 目的就是隐藏我们不希望被调用的 fib/3 的定义。 实际上,如果你把所有的数学函数都放在这个模块中,它会更有用(并且更容易记住)。 如果我们在 math 模块中放入第二个函数,那么这个第二个函数 可以调用 fib/3,这会破坏 fib/3 的封装。 不确定这真的是一件可怕的事情。>> 我们可以说:>> let fib = fun(N) -> fib(N, 1, 0) end>> in>> fib(N, A, B) when N < 2 -> A;>> fib(N, A, B) -> fib(N-1, A+B, A).>> end. 拥有 letrec(或 where)是一个很棒的主意。 实际上并不关心语法的选择 - 只是另一个要学习的东西。 模块的想法是否来自函数必须 存储在某个地方,因此我们将它们存储在一个文件中,并且我们吸取 文件(作为一个单元)到系统中,因此该文件成为一个模块? 大概,是的。 这样做既方便又熟悉。>> 如果所有文件都存储在数据库中,这会 改变事情吗? 不太可能,只要数据库始终可用,并且我 永远 不会遇到因为我没有连接而无法工作的情况。> 我越来越认为最好将 所有 函数存储在 具有唯一名称的键值数据库中。 我喜欢一个大型代码存储库/服务器的想法,但拥有所有东西 在一个命名空间中听起来像一场噩梦。 唯一名称位很有趣 - 这是一个好主意。