Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why Seq.tail is not an option

My question is when entering Seq. why is there no Seq.tail function?

In this code that does not convert a sequence to a list, there is no Seq.tail function available in the recursive function. Is it because Seq.initInfinte was used to to create the sequence, or is there another reason?

open System
let readList() =
    Seq.initInfinite (fun _ -> Console.ReadLine())
    |> Seq.takeWhile (fun s -> (s <> ""))
    |> Seq.map (fun x -> Int32.Parse(x))

let rec listLen list1 acc =
    if Seq.isEmpty list1 then
        acc
    else
        (* There is no Seq.tail available. Compile error. *)
        listLen (Seq.tail list1) (acc + 1)

[<EntryPoint>]
let main argv = 
    let inList = (readList())
    let inListLen = listLen inList 0
    printfn "%A"  inListLen

    0 // return an integer exit code

However, this works just fine. I'm confused about why Seq.tail is not available, but List.tail is available.

open System

let readList() =
    Seq.initInfinite (fun _ -> Console.ReadLine())
    |> Seq.takeWhile (fun s -> (s <> ""))
    |> Seq.map (fun x -> Int32.Parse(x))

let rec listLen list1 acc =
    if List.isEmpty list1 then
        acc
    else
        listLen (List.tail list1) (acc + 1)

[<EntryPoint>]
let main argv = 
    let inList = Seq.toList (readList())
    let inListLen = listLen inList 0
    printfn "%A"  inListLen

    0 // return an integer exit code
like image 295
octopusgrabbus Avatar asked Mar 20 '17 19:03

octopusgrabbus


2 Answers

There is no specific reason, it just was never added, that's it. Though it is available in a later version of F# (there was a large effort towards regularizing collection APIs in 4.0).

However, one could offer a common-sense argument for why Seq.tail would be marginally useful, and perhaps even dangerous. Which might actually be the reason for not adding it initially, but I don't know for sure.

You see, lists and sequences have very different representations behind the scenes.

List is a data structure that has two fields: the first element (called "head"), and the rest of the elements, which is itself just another list (called "tail"). So calling List.tail means merely taking the second field of the data structure. No complicated processing, just taking one of the data structure's fields.

Sequence, on the other hand, is basically a function (called IEnumerable.GetEnumerator) that returns a mutable data structure (called IEnumerator), which can be repeatedly "kicked" (by calling IEnumerator.MoveNext), producing the next item on each kick, and changing its internal state.
This representaron means that, in order to "drop" the first element of the sequence, one would have to take the original sequence and wrap it in another function, which, when asked to produce an IEnumerator, would get the inner sequence's IEnumerator, then kick it once, and then return to the caller. Something along these lines (pseudocode):

Tail(inner).GetEnumerator =
   let innerE = inner.GetEnumerator()
   innerE.MoveNext()
   innerE

This means that, while with a list each call to tail makes the data structure less complex (one less item, only tail remains), with sequence each call to tail would make it more complex (one more function wrapper). What's more, if you take a tail of a sequence several times in a row, and then iterate over the result, you'd still be iterating over the whole original sequence, even though logically it looks shorter to you.

Applying this to your specific case, your listLen implementation based on Seq.tail would have quadratic complexity (as opposed to the list's linear), because every time you call Seq.isEmpty, that will effectively cause iteration up to the first non-skipped item, and each recursive call to listLen will add another skipped item to iterate over.

For what it's worth, the standard .NET LINQ does actually have an equivalent operation - called .Skip, and you can totally use it from F#:

open System.Linq

let seqTail (s: _ seq) = s.Skip(1)

Or, as Robert Nielsen notes in the comments, there is actually a Seq.skip even in F# standard library (I was writing from my phone, couldn't verify it at the time):

let seqTail s = Seq.skip 1 s
like image 76
Fyodor Soikin Avatar answered Nov 09 '22 10:11

Fyodor Soikin


Seq.tail is not available in F# 3.x. It is available in F# 4.x. I verified that your code compiles in 4.0, but not 3.1.

A whole lot of functions were added in F# 4.0, to "regularize" List, Array and Seq.

https://github.com/fsharp/fslang-design/blob/master/FSharp-4.0/ListSeqArrayAdditions.md

(I noticed Option was left out in that issue, but suspect it too got some more functions at some point.)

As to why all these functions were missing, they simply lacked time to fill in those gaps in the earlier versions.

like image 43
Bent Tranberg Avatar answered Nov 09 '22 10:11

Bent Tranberg