Obviously I'm only saving 3 lines of code (in this example) and I'm getting worse runtime characteristics (which I can live with in the normal case).
Is this a flagrant abuse of map? I feel like it is, but I wanted to hear some feedback from those of you who are functional in the brain.
Maybe foreach should return the collection rather than Unit? (calm down, I know some of you get anger attacks when suggestions are made to change long standing and ubiquitous APIs, so I didn't really mean it).
Rather than getting into arguments about the aesthetic of the code (which I'll leave to someone else)…
What you're relying on here is that map is consecutive and sequential. This is something you can generally rely on for non-parallel Seq, but you're technically not supposed to. In theory, Seq is free to run the map iteration in any order that is convenient. If that semantic changes (and it could), your code will unexpectedly break.
Daniel
On Thu, Apr 5, 2012 at 9:48 AM, Nils Kilden-Pedersen <nil...@gmail.com>wrote:
> Several times I've had to produce a collection of something, iterate it > for some purpose and then return it. One way to do that, would be like this:
> def getFedMouthes(): Seq[Mouth] = {
> val mouthes = mouthProducer.getThoseMouthes() > mouthes.foreach { mouth =>
> mouth.feed(food.next)
> mouth.clean(toothbrush) // Yeah, we're using the same tooth brush
> } > mouthes
> }
> Now, that's all nice, but a little too verbose, so on occasion I've done > this (and felt oddly weird about it):
> Obviously I'm only saving 3 lines of code (in this example) and I'm > getting worse runtime characteristics (which I can live with in the normal > case).
> Is this a flagrant abuse of map? I feel like it is, but I wanted to hear > some feedback from those of you who are functional in the brain.
> Maybe foreach should return the collection rather than Unit? (calm down, I > know some of you get anger attacks when suggestions are made to change long > standing and ubiquitous APIs, so I didn't really mean it).
Iirc there's mapConserve which doesn't rebuild the collection if the transformation doesn't change any elements. I think that would remedy the runtime characteristics :)
On 5 April 2012 16:48, Nils Kilden-Pedersen <nil...@gmail.com> wrote:
> Several times I've had to produce a collection of something, iterate it > for some purpose and then return it. One way to do that, would be like this:
> def getFedMouthes(): Seq[Mouth] = {
> val mouthes = mouthProducer.getThoseMouthes() > mouthes.foreach { mouth =>
> mouth.feed(food.next)
> mouth.clean(toothbrush) // Yeah, we're using the same tooth brush
> } > mouthes
> }
> Now, that's all nice, but a little too verbose, so on occasion I've done > this (and felt oddly weird about it):
> Obviously I'm only saving 3 lines of code (in this example) and I'm > getting worse runtime characteristics (which I can live with in the normal > case).
> Is this a flagrant abuse of map? I feel like it is, but I wanted to hear > some feedback from those of you who are functional in the brain.
> Maybe foreach should return the collection rather than Unit? (calm down, I > know some of you get anger attacks when suggestions are made to change long > standing and ubiquitous APIs, so I didn't really mean it).
On Thu, Apr 5, 2012 at 10:03 AM, Daniel Spiewak <djspie...@gmail.com> wrote: > Rather than getting into arguments about the aesthetic of the code (which > I'll leave to someone else)…
> What you're relying on here is that map is consecutive and sequential. > This is something you can generally rely on for non-parallel Seq, but > you're technically not supposed to. In theory, Seq is free to run the map > iteration in any order that is convenient. If that semantic changes (and > it could), your code will unexpectedly break.
Are you saying that mapping a general Seq provides no guarantees (and shouldn't) about retaining order? That would seem counter-intuitive to me.
> On Thu, Apr 5, 2012 at 10:03 AM, Daniel Spiewak <djspie...@gmail.com>wrote:
>> Rather than getting into arguments about the aesthetic of the code (which >> I'll leave to someone else)…
>> What you're relying on here is that map is consecutive and sequential. >> This is something you can generally rely on for non-parallel Seq, but >> you're technically not supposed to. In theory, Seq is free to run the map >> iteration in any order that is convenient. If that semantic changes (and >> it could), your code will unexpectedly break.
> Are you saying that mapping a general Seq provides no guarantees (and > shouldn't) about retaining order? That would seem counter-intuitive to me.
That is correct. IF you want sequential behavior, you have to run: foo.seq.map ....
> Several times I've had to produce a collection of something, iterate it > for some purpose and then return it. One way to do that, would be like this:
> def getFedMouthes(): Seq[Mouth] = {
> val mouthes = mouthProducer.getThoseMouthes() > mouthes.foreach { mouth =>
> mouth.feed(food.next)
> mouth.clean(toothbrush) // Yeah, we're using the same tooth brush
> } > mouthes
> }
> Now, that's all nice, but a little too verbose, so on occasion I've done > this (and felt oddly weird about it):
> Obviously I'm only saving 3 lines of code (in this example) and I'm > getting worse runtime characteristics (which I can live with in the normal > case).
> Is this a flagrant abuse of map? I feel like it is, but I wanted to hear > some feedback from those of you who are functional in the brain.
> Maybe foreach should return the collection rather than Unit? (calm down, I > know some of you get anger attacks when suggestions are made to change long > standing and ubiquitous APIs, so I didn't really mean it).
Order is guaranteed to be retained, but the order in which map traverses the sequence is *not* guaranteed. Thus:
Seq(1, 2, 3) map { i => println(i); i } // => Seq(1, 2, 3)
In the above expression, the only thing that is guaranteed is that the result will be Seq(1, 2, 3). You may see the numbers printed as "2, 3, 1", "3, 1, 2" or any other permutation. Practically speaking, all of the Seq implementations *will* traverse in order, and so you will always see "1, 2, 3" (which is why your code works at all). This traversal order is the semantic you cannot rely upon.
Daniel
On Thu, Apr 5, 2012 at 10:17 AM, Nils Kilden-Pedersen <nil...@gmail.com>wrote:
> On Thu, Apr 5, 2012 at 10:03 AM, Daniel Spiewak <djspie...@gmail.com>wrote:
>> Rather than getting into arguments about the aesthetic of the code (which >> I'll leave to someone else)…
>> What you're relying on here is that map is consecutive and sequential. >> This is something you can generally rely on for non-parallel Seq, but >> you're technically not supposed to. In theory, Seq is free to run the map >> iteration in any order that is convenient. If that semantic changes (and >> it could), your code will unexpectedly break.
> Are you saying that mapping a general Seq provides no guarantees (and > shouldn't) about retaining order? That would seem counter-intuitive to me.
Well, what your example reminds me of is Kestrels, from To Mock a Mocking Bird [1] (Debasish has blogged about this before [2]). In Scala, you may define the Kestrel combinator like this:
def kestrel[A,B,C](f: A => B, g: B => C): A => B = a => { val b = f(a) g(b) b
}
For your particular use case, this isn't too useful. You may want to define something like Ruby's tap:
def tap[A,B](a: A, f: A => B): A = kestrel(identity[A], f)(a)
On Thu, Apr 5, 2012 at 11:03 AM, Daniel Spiewak <djspie...@gmail.com> wrote: > Rather than getting into arguments about the aesthetic of the code (which > I'll leave to someone else)…
> What you're relying on here is that map is consecutive and sequential. > This is something you can generally rely on for non-parallel Seq, but > you're technically not supposed to. In theory, Seq is free to run the map > iteration in any order that is convenient. If that semantic changes (and > it could), your code will unexpectedly break.
> Daniel
> On Thu, Apr 5, 2012 at 9:48 AM, Nils Kilden-Pedersen <nil...@gmail.com>wrote:
>> Several times I've had to produce a collection of something, iterate it >> for some purpose and then return it. One way to do that, would be like this:
>> def getFedMouthes(): Seq[Mouth] = {
>> val mouthes = mouthProducer.getThoseMouthes() >> mouthes.foreach { mouth =>
>> mouth.feed(food.next)
>> mouth.clean(toothbrush) // Yeah, we're using the same tooth brush
>> } >> mouthes
>> }
>> Now, that's all nice, but a little too verbose, so on occasion I've done >> this (and felt oddly weird about it):
>> Obviously I'm only saving 3 lines of code (in this example) and I'm >> getting worse runtime characteristics (which I can live with in the normal >> case).
>> Is this a flagrant abuse of map? I feel like it is, but I wanted to hear >> some feedback from those of you who are functional in the brain.
>> Maybe foreach should return the collection rather than Unit? (calm down, >> I know some of you get anger attacks when suggestions are made to change >> long standing and ubiquitous APIs, so I didn't really mean it).
eh? It doesn't guarantee traveral order on anything that'd not a Seq, but for a subclass of Seq that has a well-defined "order" to elements, .seq will make it both synchronous and in-order. Not sure why you thought it didn't....
> eh? It doesn't guarantee traveral order on anything that'd not a Seq, but > for a subclass of Seq that has a well-defined "order" to elements, .seq > will make it both synchronous and in-order. Not sure why you thought it > didn't....
> On Thu, Apr 5, 2012 at 11:24 AM, Daniel Spiewak <djspie...@gmail.com>wrote:
>> That is correct. IF you want sequential behavior, you have to run: >>> foo.seq.map ....
>> Even foo.seq.map does not guarantee traversal order, only synchronicity >> and result order.
This seems really weird. As I understand it, "for" is translated into map/flatmap/filter. Does this also mean that Scala's "for" loop doesn't give guarantees about sequentiality for, say, List? That seems vaguely bonkers.
On Thu, Apr 5, 2012 at 11:28 AM, Josh Suereth <joshua.suer...@gmail.com>wrote:
> eh? It doesn't guarantee traveral order on anything that'd not a Seq, but > for a subclass of Seq that has a well-defined "order" to elements, .seq > will make it both synchronous and in-order. Not sure why you thought it > didn't....
> On Thu, Apr 5, 2012 at 11:24 AM, Daniel Spiewak <djspie...@gmail.com>wrote:
>> That is correct. IF you want sequential behavior, you have to run: >>> foo.seq.map ....
>> Even foo.seq.map does not guarantee traversal order, only synchronicity >> and result order.
> Ah, I may be confusing foreach with map, but I didn't think I was....
> On Thu, Apr 5, 2012 at 11:28 AM, Josh Suereth <joshua.suer...@gmail.com>wrote:
>> eh? It doesn't guarantee traveral order on anything that'd not a Seq, >> but for a subclass of Seq that has a well-defined "order" to elements, .seq >> will make it both synchronous and in-order. Not sure why you thought it >> didn't....
>> On Thu, Apr 5, 2012 at 11:24 AM, Daniel Spiewak <djspie...@gmail.com>wrote:
>>> That is correct. IF you want sequential behavior, you have to run: >>>> foo.seq.map ....
>>> Even foo.seq.map does not guarantee traversal order, only synchronicity >>> and result order.
There's nothing in the contract of *map* that says anything about traversal order, only result order. The foreach function has a guaranteed traversal order, but map does not, even on sequences. I have often thought that this could be exploited in certain trie-based sequences to improve better performance, but I haven't spent much time thinking in that direction.
Daniel
On Thu, Apr 5, 2012 at 10:28 AM, Josh Suereth <joshua.suer...@gmail.com>wrote:
> eh? It doesn't guarantee traveral order on anything that'd not a Seq, but > for a subclass of Seq that has a well-defined "order" to elements, .seq > will make it both synchronous and in-order. Not sure why you thought it > didn't....
> On Thu, Apr 5, 2012 at 11:24 AM, Daniel Spiewak <djspie...@gmail.com>wrote:
>> That is correct. IF you want sequential behavior, you have to run: >>> foo.seq.map ....
>> Even foo.seq.map does not guarantee traversal order, only synchronicity >> and result order.
On Thu, Apr 5, 2012 at 10:30 AM, Luke Vilnis <lvil...@gmail.com> wrote: > This seems really weird. As I understand it, "for" is translated into > map/flatmap/filter. Does this also mean that Scala's "for" loop doesn't > give guarantees about sequentiality for, say, List? That seems vaguely > bonkers.
> On Thu, Apr 5, 2012 at 11:28 AM, Josh Suereth <joshua.suer...@gmail.com>wrote:
>> eh? It doesn't guarantee traveral order on anything that'd not a Seq, >> but for a subclass of Seq that has a well-defined "order" to elements, .seq >> will make it both synchronous and in-order. Not sure why you thought it >> didn't....
>> On Thu, Apr 5, 2012 at 11:24 AM, Daniel Spiewak <djspie...@gmail.com>wrote:
>>> That is correct. IF you want sequential behavior, you have to run: >>>> foo.seq.map ....
>>> Even foo.seq.map does not guarantee traversal order, only synchronicity >>> and result order.
List happens to be, because it's a Sequence and *not* a parallel collection.
Parallel collections have thier map/flatMap/filter operations evaluated in parallel (i.e. not sequential). But the result preserves the sequential ordering.
calling .seq on a parallel collection returns you to non-parallel behavior.
Also, collections with no deifned ordering (like Map/Set) don't have to abide by any rules in how their map/filter/flatMap operate.
On Thu, Apr 5, 2012 at 11:30 AM, Luke Vilnis <lvil...@gmail.com> wrote: > This seems really weird. As I understand it, "for" is translated into > map/flatmap/filter. Does this also mean that Scala's "for" loop doesn't > give guarantees about sequentiality for, say, List? That seems vaguely > bonkers.
> On Thu, Apr 5, 2012 at 11:28 AM, Josh Suereth <joshua.suer...@gmail.com>wrote:
>> eh? It doesn't guarantee traveral order on anything that'd not a Seq, >> but for a subclass of Seq that has a well-defined "order" to elements, .seq >> will make it both synchronous and in-order. Not sure why you thought it >> didn't....
>> On Thu, Apr 5, 2012 at 11:24 AM, Daniel Spiewak <djspie...@gmail.com>wrote:
>>> That is correct. IF you want sequential behavior, you have to run: >>>> foo.seq.map ....
>>> Even foo.seq.map does not guarantee traversal order, only synchronicity >>> and result order.
> No, you're off-base here Daniel. .seq makes it sequential and synchronous.
> On Thu, Apr 5, 2012 at 11:29 AM, Josh Suereth <joshua.suer...@gmail.com>wrote:
>> Ah, I may be confusing foreach with map, but I didn't think I was....
>> On Thu, Apr 5, 2012 at 11:28 AM, Josh Suereth <joshua.suer...@gmail.com>wrote:
>>> eh? It doesn't guarantee traveral order on anything that'd not a Seq, >>> but for a subclass of Seq that has a well-defined "order" to elements, .seq >>> will make it both synchronous and in-order. Not sure why you thought it >>> didn't....
>>> On Thu, Apr 5, 2012 at 11:24 AM, Daniel Spiewak <djspie...@gmail.com>wrote:
>>>> That is correct. IF you want sequential behavior, you have to run: >>>>> foo.seq.map ....
>>>> Even foo.seq.map does not guarantee traversal order, only synchronicity >>>> and result order.
On Thu, Apr 5, 2012 at 11:32 AM, Daniel Spiewak <djspie...@gmail.com> wrote: > There's nothing in the contract of *map* that says anything about > traversal order, only result order. The foreach function has a guaranteed > traversal order, but map does not, even on sequences. I have often thought > that this could be exploited in certain trie-based sequences to improve > better performance, but I haven't spent much time thinking in that > direction.
> Daniel
> On Thu, Apr 5, 2012 at 10:28 AM, Josh Suereth <joshua.suer...@gmail.com>wrote:
>> eh? It doesn't guarantee traveral order on anything that'd not a Seq, >> but for a subclass of Seq that has a well-defined "order" to elements, .seq >> will make it both synchronous and in-order. Not sure why you thought it >> didn't....
>> On Thu, Apr 5, 2012 at 11:24 AM, Daniel Spiewak <djspie...@gmail.com>wrote:
>>> That is correct. IF you want sequential behavior, you have to run: >>>> foo.seq.map ....
>>> Even foo.seq.map does not guarantee traversal order, only synchronicity >>> and result order.
On Thu, Apr 5, 2012 at 12:23, Daniel Spiewak <djspie...@gmail.com> wrote: > Order is guaranteed to be retained, but the order in which map traverses the > sequence is not guaranteed. Thus:
> Seq(1, 2, 3) map { i => println(i); i } // => Seq(1, 2, 3)
> In the above expression, the only thing that is guaranteed is that the > result will be Seq(1, 2, 3). You may see the numbers printed as "2, 3, 1", > "3, 1, 2" or any other permutation. Practically speaking, all of the Seq > implementations will traverse in order, and so you will always see "1, 2, 3" > (which is why your code works at all). This traversal order is the semantic > you cannot rely upon.
Let me make something clear before things get too confused: the above example will ALWAYS print 1, 2, 3. Seq is guaranteed not to be parallel. It is GenSeq that allows the behavior described.
On Thu, Apr 5, 2012 at 11:33 AM, Daniel Spiewak <djspie...@gmail.com> wrote: > That would surprise me. It would make Scala the only collections library > that makes a *hard* guarantee about the traversal order of the map > function.
> Daniel
> On Thu, Apr 5, 2012 at 10:31 AM, Josh Suereth <joshua.suer...@gmail.com>wrote:
>> No, you're off-base here Daniel. .seq makes it sequential and >> synchronous.
>> On Thu, Apr 5, 2012 at 11:29 AM, Josh Suereth <joshua.suer...@gmail.com>wrote:
>>> Ah, I may be confusing foreach with map, but I didn't think I was....
>>> On Thu, Apr 5, 2012 at 11:28 AM, Josh Suereth <joshua.suer...@gmail.com>wrote:
>>>> eh? It doesn't guarantee traveral order on anything that'd not a Seq, >>>> but for a subclass of Seq that has a well-defined "order" to elements, .seq >>>> will make it both synchronous and in-order. Not sure why you thought it >>>> didn't....
>>>> On Thu, Apr 5, 2012 at 11:24 AM, Daniel Spiewak <djspie...@gmail.com>wrote:
>>>>> That is correct. IF you want sequential behavior, you have to run: >>>>>> foo.seq.map ....
>>>>> Even foo.seq.map does not guarantee traversal order, only >>>>> synchronicity and result order.
If we're going to make that assumption, then it needs to be documented. As I said, there is absolutely nothing in the contract of map (on Seq *or*GenSeq) that says anything about traversal order, only synchronicity and result order.
Daniel
On Thu, Apr 5, 2012 at 10:34 AM, Josh Suereth <joshua.suer...@gmail.com>wrote:
> While that's true, if you were to do that in Scala's collections library, > expect to break things.
> - Josh
> On Thu, Apr 5, 2012 at 11:32 AM, Daniel Spiewak <djspie...@gmail.com>wrote:
>> There's nothing in the contract of *map* that says anything about >> traversal order, only result order. The foreach function has a guaranteed >> traversal order, but map does not, even on sequences. I have often thought >> that this could be exploited in certain trie-based sequences to improve >> better performance, but I haven't spent much time thinking in that >> direction.
>> Daniel
>> On Thu, Apr 5, 2012 at 10:28 AM, Josh Suereth <joshua.suer...@gmail.com>wrote:
>>> eh? It doesn't guarantee traveral order on anything that'd not a Seq, >>> but for a subclass of Seq that has a well-defined "order" to elements, .seq >>> will make it both synchronous and in-order. Not sure why you thought it >>> didn't....
>>> On Thu, Apr 5, 2012 at 11:24 AM, Daniel Spiewak <djspie...@gmail.com>wrote:
>>>> That is correct. IF you want sequential behavior, you have to run: >>>>> foo.seq.map ....
>>>> Even foo.seq.map does not guarantee traversal order, only synchronicity >>>> and result order.
.seq guarantees single-threadedness. In practice it also provides traversal order. I don't see any documentation to the effect that it * guarantees* traversal order.
On Thu, Apr 5, 2012 at 11:37 AM, Daniel Spiewak <djspie...@gmail.com> wrote: > If we're going to make that assumption, then it needs to be documented. > As I said, there is absolutely nothing in the contract of map (on Seq *or*GenSeq) that says anything about traversal order, only synchronicity and > result order.
> Daniel
> On Thu, Apr 5, 2012 at 10:34 AM, Josh Suereth <joshua.suer...@gmail.com>wrote:
>> While that's true, if you were to do that in Scala's collections library, >> expect to break things.
>> - Josh
>> On Thu, Apr 5, 2012 at 11:32 AM, Daniel Spiewak <djspie...@gmail.com>wrote:
>>> There's nothing in the contract of *map* that says anything about >>> traversal order, only result order. The foreach function has a guaranteed >>> traversal order, but map does not, even on sequences. I have often thought >>> that this could be exploited in certain trie-based sequences to improve >>> better performance, but I haven't spent much time thinking in that >>> direction.
>>> Daniel
>>> On Thu, Apr 5, 2012 at 10:28 AM, Josh Suereth <joshua.suer...@gmail.com>wrote:
>>>> eh? It doesn't guarantee traveral order on anything that'd not a Seq, >>>> but for a subclass of Seq that has a well-defined "order" to elements, .seq >>>> will make it both synchronous and in-order. Not sure why you thought it >>>> didn't....
>>>> On Thu, Apr 5, 2012 at 11:24 AM, Daniel Spiewak <djspie...@gmail.com>wrote:
>>>>> That is correct. IF you want sequential behavior, you have to run: >>>>>> foo.seq.map ....
>>>>> Even foo.seq.map does not guarantee traversal order, only >>>>> synchronicity and result order.
> It seems the documentation in the scaladocs is lacking. That's something > we can correct.
> On Thu, Apr 5, 2012 at 11:37 AM, Daniel Spiewak <djspie...@gmail.com>wrote:
>> If we're going to make that assumption, then it needs to be documented. >> As I said, there is absolutely nothing in the contract of map (on Seq *or >> * GenSeq) that says anything about traversal order, only synchronicity >> and result order.
>> Daniel
>> On Thu, Apr 5, 2012 at 10:34 AM, Josh Suereth <joshua.suer...@gmail.com>wrote:
>>> While that's true, if you were to do that in Scala's collections >>> library, expect to break things.
>>> - Josh
>>> On Thu, Apr 5, 2012 at 11:32 AM, Daniel Spiewak <djspie...@gmail.com>wrote:
>>>> There's nothing in the contract of *map* that says anything about >>>> traversal order, only result order. The foreach function has a guaranteed >>>> traversal order, but map does not, even on sequences. I have often thought >>>> that this could be exploited in certain trie-based sequences to improve >>>> better performance, but I haven't spent much time thinking in that >>>> direction.
>>>> Daniel
>>>> On Thu, Apr 5, 2012 at 10:28 AM, Josh Suereth <joshua.suer...@gmail.com >>>> > wrote:
>>>>> eh? It doesn't guarantee traveral order on anything that'd not a Seq, >>>>> but for a subclass of Seq that has a well-defined "order" to elements, .seq >>>>> will make it both synchronous and in-order. Not sure why you thought it >>>>> didn't....
>>>>> On Thu, Apr 5, 2012 at 11:24 AM, Daniel Spiewak <djspie...@gmail.com>wrote:
>>>>>> That is correct. IF you want sequential behavior, you have to run: >>>>>>> foo.seq.map ....
>>>>>> Even foo.seq.map does not guarantee traversal order, only >>>>>> synchronicity and result order.