At the risk of catching HN's ire... this is not a good thing.
Haskell's type signature tells you almost nothing about whether this use-case is supported. If it's not, you've got yourself a nonlinear slowdown, potentially wrecking your performance. Readers can't know this without literally reading the documentation, except the documentation doesn't even tell you if this is OK.
Yet because people are being ‘clever’ like this, Haskell has ended up stuck with a sort a factor ~ten slower than Python (!!), and an incremental sort that's still a factor ~2 slower than Python's heapq-based incremental sort.
So you're using a sort in a way that you have no explicit language-level guarantees for, seemingly no documented guarantees for at all, and in a way that's incredibly harmful to the common case, but also—and this is a curse that's almost unique to Haskell—the language is also prevented from utilizing future improvements to sorting algorithms!
Because of this, and despite Haskell's ‘purity’, the internals of Haskell's sort are more observable and less open to change than is the case for almost any other language that I know of!
Back in reality of course, Haskell has fast, generic, linear-time sorting: https://hackage.haskell.org/package/discrimination
Laziness enables many compositions to work efficiently, whereas with a strict language, you'd have to fuse the operations to get the same efficiency. More examples here: http://augustss.blogspot.com/2011/05/more-points-for-lazy-ev...
Is your k here representing “take k . sort”?
In other words, the very nature of Haskell's lazy-by-default semantics makes the straight-forward, seemingly strict implementation of quicksort, or other sorting algorithm, automatically optimized to improve performance through lazy execution.
That’s the kind of thing you would use “if” for right?
This should be read from right to left: it sorts an (implicit) list, then reads the first 10 elements from the beginning of the now-sorted list, and throws away the rest.
Except under the hood in Haskell, sort is not a function that takes a list and returns a list. It is a function that takes a list, and returns the smallest element of that list and a function to fetch the rest of the sorted list. (A function which actually returns the second-smallest element and a function that if called returns the third smallest element and another function, etc.)
These ephemeral functions / continuations / "thunks" are allowed to have internal state and thereby perform more complex sorting operations such as quicksort, and might actually be represented by an internal tree of branch points and their returned value or unexecuted thunks.
But all of this complexity is hidden by the compiler. It's not some special property of `sort` either, which might look like this:
sort [] = []
sort (x:xs) = sort small ++ (x : sort large)
where small = [y | y <- xs, y <= x]
large = [y | y <- xs, y > x]
"The sort of an empty list is the empty list. The sort of a non-empty list is the elements of the tail less than or equal to the head of the list, sorted, followed by the head of the list, followed by the elements of the tail greater than the head of the lest, sorted."The Haskell compiler does the magic of turning this into a lazy-optimized implementation which in the case of `take 10 . sort` doesn't bother to calculate any remaining `sort large` partitions once the first 10 smallest elements have been found.