Erlang中的函数链接

myzjeezk  于 2022-12-08  发布在  Erlang
关注(0)|答案(4)|浏览(147)

创建像Active Record或Hibernate这样的ORM会很好,它应该像这样处理链接查询:

User = User:new():for_login(«stackoverflow_admin»):for_password(«1984»):load().

我们怎么能做到这一点呢?或者就像那样,在一行--或者至少在精神和意义上相似。
也许有一些预处理工具,可以帮助在这方面?

aiazj4mn

aiazj4mn1#

"Chaining" is a confused form of functional composition, sometimes relying on return values, sometimes operating directly over state mutations. This is not how Erlang works.
Consider:

f(g(H))

is equivalent to:

G = g(H),
f(G)

but may or may not be equivalent to:

g(H).f()

In this form functions are stacked up, and the operations proceed "toward" the return-value side (which is nearly always the left-hand side in most programming languages). In languages where OOP is forced on the programmer as the sole paradigm available (such as Java), however, this function-flowing form is very often not possible without excess noise because of the requirement that all functions (class methods) be essentially namespaced by a class name, whether or not any objects are involved:

F.f(G.g(h.get_h()))

More typically in Java operations over the data are added to the object itself and as much data as possible is held in object instances. Transform methods do not have a "return" value in quite the same way, they instead mutate the internal state, leaving the same object but a "new version" of it. If we think of a mutating method as "returning a new version of the object" then we can think of the dot operator as a bastardized functional composition operator that makes values sort of "flow to the right" as the mutated object is now having additional methods invoked, which may continue to mutate the state as things move along:

query.prepare(some_query).first(100).sort("asc")

In this case the execution "flow" moves to the right, but only because the concept of functional composition has been lost -- we are "ticking" forward along a chain of mutating state events instead of using actual return values. That also means that in these languages we can get some pretty weird flip-flops in the direction of execution if we take stacking too far:

presenter.sort(conn.perform(query.prepare(some_query)).first(100), "asc")

Quick, at a glance tell me what the inner-most value is that kicks that off?
This is not a particularly good idea.
Erlang does not have objects, it has processes, and they don't work the quite way you are imagining above. (This is discussed at length here: Erlang Process vs Java Thread .) Erlang processes cannot call one another or perform operations against one another -- they can only send messages, spawn, monitor and link. That's it. So there is no way for an "implicit return" value (such as in the case of a chain of mutating object values) to have an operation defined over it. There are no implicit returns in Erlang: every operation in Erlang has an explicit return.
In addition to explicit returns and a computational model of isolated-memory concurrent processes instead of shared-memory objects, Erlang code is typically written to "crash fast". Most of the time this means that nearly any function that has a side effect returns a tuple instead of a naked value. Why? Because the assignment operator is also a matching operator, which also makes it an assertion operator. Each line in a section of a program that has side-effects is very often written in a way to assert that an operation was successful or returned an expected type or crash immediately. That way we catch exactly where the failure happened instead of proceeding with possibly faulty or missing data (which happens all the time in OOP chained code -- hence the heavy reliance on exceptions to break out of bad cases).
With your code I'm not sure if the execution (and my eye, as I read it) is supposed to flow from the left to the right, or the other way around:

User = User:new():for_login(«stackoverflow_admin»):for_password(«1984»):load().

An equivalent that is possible in Erlang would be:

User = load(set_password(set_uid(user:new(), "so_admin") "1984"))

But that's just silly. From whence have these mysterious literals arrived? Are we going to call them in-line:

User = load(set_password(set_uid(user:new(), ask_uid()) ask_pw()))

That's going to be pretty awkward to extract yourself from if the user enters an invalid value (like nothing) or disconnects, or times out, etc. It will also be ugly to debug when a corner case is found -- what call to what part failed and how much stuff unrelated to the actual problem is sitting on the stack now waiting for a return value? (Which is where exceptions come in... more on that waking nightmare below.)
Instead the common way to approach this would be something similar to:

register_new_user(Conn) ->
    {ok, Name} = ask_username(Conn),
    {ok, Pass} = ask_password(Conn),
    {ok, User} = create_user(Name, Pass),
    User.

Why would we do this? So that we know when this crashes exactly where it happened -- that will go a long way to telling us how and why it happened. If any of the return values are not a tuple of the shape {ok, Value} the process will crash right there (and most of the time that means taking the connection with it -- which is a good thing). Consider how much exception handling a side-effecty procedure like this actually requires in a language like Java. The long-chain one-liner suddenly becomes a lot more lines:

User =
    try
        User:new():for_login("so_admin"):for_password("1984"):load()
    catch
        {error, {password, Value}} ->
            % Handle it
        {error, {username, Value}} ->
            % Handle it
        {error, db_create} ->
            % Handle it
        {error, dropped_connection} ->
            % Handle it
        {error, timeout} ->
            % Handle it
        %% Any other errors that might possible happen...
    end.

这是一个超级恼人的结果不确定(甚至过长)的组成:它将所有的错误情况堆积在一起,必须通过传播异常来处理它们。如果在这些调用中不抛出上述坏情况下的异常,那么您就不知道哪里出错了,一行解决方案的成本是 * 至少 * 额外添加了12行 * 只到这一个过程 *,我们甚至还没有解决在这些错误处理程序中应该发生什么!
这就是Erlang的“让它崩溃”哲学的光辉之处。代码可以用一种只做成功假设的方式来编写 * 只要我们坚持这些假设 *。错误处理代码可以从其他地方提取出来(主管)和状态可以恢复到主业务逻辑之外的已知条件。采用这一点可以创建健壮的并发系统,忽视它会产生易碎的晶体。
然而,所有这一切的代价就是那些一行Assert。在并发程序中,这是一个非常有益的权衡。

disho6za

disho6za2#

虽然我发现@zxq9的答案信息量很大,但提到在Erlang中模仿类Java OOP风格的历史可能会有所帮助。
方法链需要 * state *,并且已经努力将"有状态模块"包括到Erlang中:

  • Parametrized Module:允许开发人员实现接受参数的模块,并将其作为模块状态保存在函数中使用的建议。很久以前,它作为一个实验特性被添加到Erlang中,但技术板decided to remove在R16中对该特性的语法支持,因为它导致了概念上和实际上的不兼容。
  • Tuple Module:参数化模块的向后兼容性,在Programming Erlang (Second Edition)的第8章中也有详细介绍,但没有官方文档,它仍然是一个controversial feature,引入了复杂性和模糊性,没有Erlang方式的能力。

在Erlang(一种具有不同概念的函数式语言)中模仿具有不同范例的语言(如Ruby和Java)的编码风格可能令人兴奋,但没有附加值。

2izufjch

2izufjch3#

看一下BossDB,它是一个编译器链和运行时库,用于通过Erlang参数化模块访问数据库。

a11xaf1n

a11xaf1n4#

如果参数数目相同,则此模式有效:

chain(Val, []) -> Val;
chain(Val, [H|T]) ->
    chain(H(Val), T).

示例:

9> example:chain(1, [fun(X) -> X + 1 end, fun(X) -> X + 10 end]).
12
10> example:chain("a", [fun(X) -> X ++ "b" end, fun(X) -> X ++ "c" end]).
"abc"
11> example:chain(<<1,2,4>>, [fun binary:bin_to_list/1, fun lists:reverse/1, fun binary:list_to_bin/1]).
<<4,2,1>>

相关问题