Принятие идентификатора уникально:
var result = xmldoc.Element("Customers")
.Elements("Customer")
.Single(x => (int?)x.Attribute("ID") == 2);
Вы могли также использовать First
, FirstOrDefault
, SingleOrDefault
или Where
, вместо Single
для различных обстоятельств.
A tail recursive call is more of a "return and immediately call this other function" than a goto because of the housekeeping that's performed.
Addressing your newest points: recording the return point is just one bit of housekeeping that's performed when a function is called. The return point is stored in the stack frame, the rest of which must be allocated and initialized (in a normal call), including the local variables and parameters. With tail recursion, a new frame doesn't need to be allocated and the return point doesn't need to be stored (the previous value works fine), but the rest of the housekeeping needs to be performed.
There's also housekeeping that needs to be performed when a function returns, which includes discarding locals and parameters (as part of the stack frame) and returning to the call point. During tail recursive call, the locals for the current function are discarded before invoking the new function, but no return happens.
Rather like how threads allow for lighter-weight context switching than processes, tail calls allow for lighter-weight function invocation, since some of the housekeeping can be skipped.
The "goto &NAME
" statement in Perl is closer to what you're thinking of, but not quite, as it discards locals. Parameters are kept around for the newly invoked function.
One more, simple difference: a tail call can only jump to a function entry point, while a goto can jump most anywhere (some languages restrict the target of a goto, such as C, where goto can't jump outside a function).
У вас есть два вопроса.
Во-первых, нет, в этом случае вам не грозит переполнение стека, потому что оба эти вызова test () являются хвост-рекурсивный .
Во-вторых, нет, вызовы функций - это не переходы, это вызовы функций. :) Проблема с goto заключается в том, что он обходит любую структуру в вашем коде. Вы можете выпрыгивать из утверждений, переходить к ним, обходить задания ... все виды вздора. Вызовы функций не имеют этой проблемы, потому что они имеют очевидный поток управления.
It's a goto
in the same why that if
is goto
and while
is goto
. It is implemented using (the moral equivalent of) goto
, but it does not expose the full shoot-self-in-foot potential of goto
directly to the programmer.
Я думаю, что разница здесь между «настоящим» goto и тем, что в некоторых случаях может показаться goto. В некоторых особых случаях компилятор может определить, что он может очистить стек текущей функции перед вызовом другой функции. Это когда вызов является последним вызовом функции. Разница, конечно же, в том, что, как и в любом другом вызове, вы можете передавать аргументы новой функции.
Как отмечали другие, эта оптимизация не ограничивается рекурсивными вызовами, а всеми последними вызовами. Это используется в «классическом» способе программирования конечных автоматов.
В этом случае можно выполнить оптимизацию хвостового вызова, поскольку нам не нужно выполнять дополнительную работу или использовать локальные переменные. Таким образом, компилятор преобразует это в цикл.
Фактически, эти рекурсивные функции являются конечным GOTO согласно Гаю Стилу.
(на мой взгляд, goto - это вызов функции без возможности возврата туда, откуда вы пришли). Именно это и происходит в примере с erlang.
Это не то, что происходит в Erlang, вы МОЖЕТЕ вернуться туда, откуда пришли.
Вызовы хвостовой рекурсии, что означает, что это «своего рода» goto. Убедитесь, что вы понимаете, что такое хвостовая рекурсия, прежде чем пытаться понять или написать какой-либо код. Прочитать книгу Джо Армстронга, вероятно, неплохая идея, если вы новичок в Erlang.
По идее, в случае, когда вы вызываете себя с помощью test (), выполняется вызов функции начала с использованием любых параметров, которые вы передали (в данном примере нет), но больше ничего не добавляется в стек. Таким образом, все ваши переменные выбрасываются, и функция запускается заново, но вы этого не сделали. t поместить новый указатель возврата в стек. Таким образом, это похоже на гибрид между goto и вызовом функции в традиционном императивном стиле языка, как в C или Java. Но есть еще одна запись в стеке с самого первого вызова из вызывающей функции. Поэтому, когда вы в конечном итоге выходите, возвращая значение, а не выполняя другой test (), это место возврата извлекается из стека, и выполнение возобновляется в вашей вызывающей функции.
Here's a more general answer, which supercedes my earlier answer based on call-stacks. Since the earlier answer has been accepted, I won't replace the text.
Some architectures don't have things they call "functions" that are "called", but do have something analogous (messaging may call them "methods" or "message handlers"; event based architectures have "event handlers" or simply "handlers"). I'll be using the terms "code block" and "invocation" for the general case, though (strictly speaking) "code block" can include things that aren't quite functions. You can substitute the appropriately inflected form of "call" for "invocation" or "invoke", as I might in a few places. The features of an architecture that describe invocation are sometimes called "styles", as in "continuation passing style" (CPS), though this isn't previously an official term. To keep things from being too abstract, we'll examine call stack, continuation passing, messaging (à la OOP) and event handling invocation styles. I should specify the models I'm using for these styles, but I'm leaving them out in the interest of space.
Hohpe identifies three nicely alliterative invocation features of the call-stack style: Continuation, Coordination, Context (all capitalized to distinguish them from other uses of the words).
The three features aren't necessarily independent; invocation style determines their interrelationships. For instance, Coordination is tied to Continuation under the call-stack style. Continuation and Context are connected in general, since return values are involved in Continuation.
Hohpe's list isn't necessarily exhaustive, but it will suffice to distinguish tail-calls from gotos. Warning: I might go off on tangents, such as exploring invocation space based on Hohpe's features, but I'll try to contain myself.
Each invocation feature involves tasks to be completed when invoking a code block. For Continuation, invoked code blocks are naturally related by a chain of invoking code. When a code block is invoked, the current invocation chain (or "call chain") is extended by placing a reference (an "invocation reference") to the invoking code at the end of the chain (this process is described more concretely below). Taking into account invocation also involves binding names to code blocks and parameters, we see even non-bondage-and-discipline languages can have the same fun.
Tail calling is all about optimizing Continuation, and it's a matter of recognizing when the main Continuation task (recording an invocation reference) can be skipped. The other feature tasks stand on their own. A "goto" represents optimizing away tasks for Continuation and Context. That's pretty much why a tail call isn't a simple "goto". What follows will flesh out what tail calls look like in various invocation styles.
Different styles arrange invocation chains in different structures, which I'll call a "tangle", for lack of a better word. Isn't it nice that we've gotten away from spaghetti code?
With event handling, invocations don't have responses and handlers don't wait, so "invocation chains" (as used above) isn't a useful concept. Instead of a tangle, you have priority queues of events, which are owned by channels, and subscriptions, which are lists of listener-handler pairs. In some event driven architectures, channels are properties of listeners; every listener owns exactly one channel, so channels become synonymous with listeners. Invoking means firing an event on a channel, which invokes all subscribed listener-handlers; parameters are passed as properties of the event. Code that would depend on a response in another style becomes a separate handler under event handling, with an associated event. A tail call would be a handler that fires the event on another channel and does nothing else afterwards. Tail call optimization would involve re-subscribing listeners for the event from the second channel to the first, or possibly having the handler that fired the event on the first channel instead fire on the second channel (an optimization made by the programmer, not the compiler/interpreter). Here's what the former optimization looks like, starting with the un-optimized version.
And the optimized version:
Note tail calls are trickier (untenable?) under event handling because they have to take into account subscriptions. If Alice were later to unsubscribe from "inauguration" on BBC News, the subscription to inauguration on CNN would also need to be canceled. Additionally, the system must ensure it doesn't inappropriately invoke a handler multiple times for a listener. In the above optimized example, what if there's another handler for "inauguration" on CNN that fires "inauguration" on BBC News? Alice's "party" event will be fired twice, which may get her in trouble at work. One solution is to have *Bob unsubscribe all listeners from "inauguration" on BBC News in step 4, but then you introduce another bug wherein Alice will miss inauguration events that don't come via CNN. Maybe she wants to celebrate both the U.S. and British inaugurations. These problems arise because there are distinctions I'm not making in the model, possibly based on types of subscriptions. For instance, maybe there's a special kind of one-shot subscription (like System-V signal handlers) or some handlers unsubscribe themselves, and tail call optimization is only applied in these cases.
You could go on to more fully specify invocation feature tasks. From there, you could figure out what optimizations are possible, and when they can be used. Perhaps other invocation features could be identified. You could also think of more examples of invocation styles. You could also explore the dependencies between invocation features. For instance, synchronous and asynchronous invocation involve explicitly coupling or uncoupling Continuation and Coordination. It never ends.
Get all that? I'm still trying to digest it myself.
References: