-
Notifications
You must be signed in to change notification settings - Fork 556
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Feature] Sequence.first #1139
Comments
I've often thought about it, the interesting question is the lack of symmetry with |
Yeah. While it's much less common, last would be nice too. The implementation is a bit more tricky though. For a sequence that can be indexed like List, you would want to prefer that to get the value more efficiently. |
I would say no to that addition. We already have `sequenceable[index]` and
`sequenceable.where(unaryPredicate)[index]` with index 0 and -1 to achieve
the same results.
Also if `first` was added, what about `second`, `third`...
Also that naming implies ordering which is not implied by the proposed
implementation (which would produce "invalid" results for maps).
|
Ah okay, the sequence[index] meets what I want, just to get a single item from a sequence. Is it just not added yet then? I'm not sure exactly what I'm looking for, but I couldnt see any PRs. I just tested in wren_cli its not in 0.4.0. |
Sequences aren't in general indexable (for example Range isn't) so you have to convert to a list first: var lst = [6, 7, 8, 9]
var first = lst.where { |e| e > 6 }.toList[0]
System.print(first) // 7 The trouble with this is that it's not very efficient. You have to iterate through the entire sequence to find out which elements satisfy the predicate, add those to a new list and then extract the first one. Fot this reason, I would be in favor of your proposal as you only need to iterate the sequence until you find an element which satisfies the predicate and then return that directly. |
Out of curiosity, what is your use case?
The real general case would be to use `sequence.iteratorValue(sequence.iterate(null))`.
Unless I'm mistaken, there is no guarantee that it will output a predicable output (in particular since maps can/is nordered). So it defeat the point in the name choice.
|
Although you have a point about 'first' implying ordering, in my experience languages which have similar fluent APIs to Wren (C#, Kotlin etc.) do still use the word 'first' in the sense that it returns the first element in the iteration which satisfies the predicate. How useful this is will depend on the nature of the sequence though I agree that it may be useless for maps whose iteration order is undefined unless you simply want to extract some random key/value pair which satisfies the predicate. Also those languages do generally have a 'last' method as well though I can't think of one which has second, third etc. Although you'd need to iterate through the whole sequence to find the last element it would still be more efficient than 'where' because you wouldn't need to do Possible implementations for Wren: last {
var final = null
for (element in this) {
final = element
}
return final
}
last(a, f) {
var final = null
for (element in this) {
if (f.call(element)) {
final = element
}
}
return final
} |
In C++, it's called |
@PureFox48 These implementations are |
Yes, I wondered about that - I have a reverse iterator in my own modules, though we don't have one as yet in the core library.
|
|
I'd avoided suggesting But we could perhaps use |
|
You could implement it like that but it would produce an intermediate WhereSequence which @Deijin27's suggested implementation does not do. On the other hand it would add less code to core and |
I don't mind what it's called, it's the functionality that matters. A second or third wouldn't be necessary, as you can combine it with skip(1) skip(2) for this rare case. I've had a think about my use case, and general use cases in any language // if a matching item is found, handle it, otherwise return
// handling it in the loop often leads to pyramid code, so it's common to have
// the 'failures' return, and the 'successful' path be the last thing in the method
// if you need a loop, even the where feels unnecessary when it could be replaced with an if
// the example further down is probably a better case to convey why you might
// only want or expect a single matching item
// but it's also reasonable that one might want any singlular Frog
var foundFrog = null
for (frog in amphibians.where {|amphibian| amphibian is Frog }) {
foundFrog = frog
break
}
if (foundFrog == null) return
handleFrog(firstFrog)
var firstFrog = amphibians.first {|amphibian| amphibian is Frog }
if (foundFrog == null) return
handleFrog(firstFrog)
// you have a list which could be empty, you want
// a single item, in this example to be a default "selected" item
// the indexer throws an exception if the list is empty,
// so you have less simple code
var selectedAmphibian = null
if (amphibians.count > 0) {
selectedAmphibian = amphibians[0]
}
var selectedAmphibian = amphibians.first
// find an item, where a property is generally unique to an item
// but it's a property on that item, rather than being enforced via
// a map. Or maybe it's multiple items that are unique as a whole.
// Consider an collection with firstName and surname
var people = [
Person.new("Kermit", "TheFrog"),
Person.new("Froggy", "McFrogFace")
]
var kermitTheFrog = null
for (person in people.where {|x| x.firstName == "Kermit" && x.surname == "TheFrog"}) {
kermitTheFrog = person
break
}
var kermitTheFrog = people.first {|x| x.firstName == "Kermit" && x.surname == "TheFrog" } A specific case I have in wren is my xml library. #doc = "The first and only node in this document that is an XElement. null if there is no XElement in the document."
root {
for (node in nodes) {
if (node is XElement) {
return node
}
}
return null
} And from a user's point of view, an xml element has child nodes that are elements or comments, and thus the elements property is always a WhereSequence to filter out the comments. When processing you often have similar cases to the kermit example, since xml is commonly used for serialization of such data. // example from readme. Finding a the color of a specific fish in the xml document
var colorOfFishCalledPearl = doc
.root
.elements("danio")
.where {|e| e.attribute("name").value == "pearl" }
.toList[0]
.attribute("color")
.value I am considering adding various methods that take a function argument, but that wouldn't be necessary wren had the first method; that combined with the already existing where and map would be perfect, and my library would nicely slot into wren's infrastructure. |
The real question is why do you need to handle only the first entry. I mean when you are dealing with a list of item, either:
|
@mhermier It exists in many APIs for a reason. Sometimes, you just want an item in a sequence and you pick the first to come. |
For me, instead it should be name more something like "peekValue" (that throw) with a compagnon "peekValueOr" (that has a default value), since it removes the notion of position in the naming. |
Well, a |
It has an intrinsic order which is necessary for traversal, not a stable order in the sense that inserting objects in some order guarantee the traversal order in every |
I think the examples I gave show how its useful, and they are examples with ordered sequences. Lists or filtered lists. I dont think I've ever used it on the equivalent of a Map in other programming languages, its just there by coincidence. Skip is comparable to this. Since the Map's enumeration order is not consistent, skip is not useful for it. |
It is true, the API is somehow broken/inconsistent in some aspects. It doesn't take into account some |
When most programmers see the word I see no reason to use If we also adopt @jube's idea of using Another advantage of |
I proposed About |
Well the only way that We could check explicitly for that with There are doubtless other methods in core where returning As you suggest, a better solution would be to return an |
Incidentally, a simple way to deal with undefined values would be to have a convention whereby you returned some obscure control code which would never be used in practice these days to denote a failure. A possible candidate would be NAK ('Negative Acknowledgement') which is "\x15" and, if one goes way back, was actually known as ERR at one time. |
A more OO solution which has just occurred to me would be to add a small singleton class to core: class Undefined {
// Returns the singleton. If it hasn't been created, creates it first.
static value { __instance == null ? __instance = Undefined.new_() : __instance }
// Private constructor.
construct new_() {}
} You could then return |
|
Well, if there's no good reason why it can't be exposed, then I think that would be the best solution.
Technically and analogous to |
I see what you're getting at with there needing to be a way to distinguish between a not found and a found null value. I just wanted to say if you were doing the singleton value approach, you just use the class itself. class Undefined {}
peek {
for (value in this) {
return value
}
return Undefined
} |
Yes, we have a discriminatory equality but to be useful as a null optional it should have a dedicated operator. The question of exposing it was already discussed with @munificent, who didn't wanted to expose another null. It is a complex topic since handling null values is a hot potato. There are many reasons to have them or not mostly based on error handling decisions, like throwing or monad... Monad are interesting because they trigger compile time error, but since we don't have that, it stays a political decision at how to handle error handling and deal with default/fallback values. But if we go monad we can remove |
Yes, that would work. Instead of |
@datatypevoid Can I have a thumb emoji please? I don't mind which one, I just feel left out. :) On a more serious note, this has come a long way from the initial suggestion. Would you consider adding a "peek" method to Sequence with a simple implementation, and moving the greater discussion to it's own thread? Could be a peek that returns null. |
For now the implementation should be something like: peek() {
var iterator = iterate(null)
if (iterator) errorHandlingToBeDefined
return iteratorValue(iterator)
} or if we simply throw it can be reduced to: peek() { iteratorValue(iterate(null)) } |
If it were up to me (which, thankfully, I hear everyone say it isn't!), then I'd implement peek {
for (value in this) {
return value
}
return null
} This at least would be consistent with the way all other methods in core use We can then decide later what if anything to do about |
Specifically in the situation that the predicate variant is included (I'm fine if not, where(fn).peek is fine). Peek sounds odd. peek(condition), "peek for a value that meets the condition" is a stretch I thought about "next" "next value that meets the condition", but for the non-predicate one it implies a state in my head, like if you called it twice you would get a different value each time. "single" might be good. "single value that meets the condition" "get a single value". But it might be too abstract from the fact that in many sequences it will give you a consistent value evem if there is multiple items. Or of course, just be a different name like findValue, but you lose the assocoation between the two. |
@PureFox48 your statement is false, seeking a non existing element in an array and map throws. @Deijin27 |
It throws if the index is out of range in the List. It returns null if the key isnt found in a map. I dont think this is an inconsistency. |
I agree that I'm not keen on
Which statement is that? For the avoidance of any doubt, I'm only suggesting we look at methods which currently return As @Deijin27 has just said, seeking a non existing element in a map returns |
I've just had a quick look through all the core and CLI modules to find out which methods return
Of these the last two aren't a problem - The first three are ambiguous as This makes me wonder whether we are getting over-excited about introducing some kind of User defined modules can, of course, deal with this situation in any way they please and, as already noted in this issue, there are several ways which don't use |
If it is ambiguous then it is not well defined. By that I mean, if we use null to encode a valid value, an error, or as an optional place holder, we can't use an API correctly and intuitively. |
I agree that it's not ideal but there are other things in Wren which, due to the simplicity of the language, we accept - sometimes reluctantly - as 'best efforts'. Having chewed this over at length, I've come to the conclusion that we should accept the possibly ambiguous use of 'null' as a best effort (at least in core). There are, after all, ways to check if it's a 'real' value or not and, even with this proposal, you could use There doesn't seem to be much appetite for exposing The alternatives - using an obscure sentinel value, using a singleton value or a singleton class object - all suffer from being 'truthy' under the current rules so, if you're using 'falsiness' to check for an undefined value (even in a third party module), you'd need to change your code to check for undefined values explicitly. Also the 'singleton' solutions are not backwards compatible ( Out of curiosity, as you obviously feel strongly about this, what is your preferred solution? |
I really like the fluent sequence api that wren has.
But I feel like a method to extract a single item from a sequence is missing.
Possible implementation
Do people think this would be a good addition?
The text was updated successfully, but these errors were encountered: