Skip to content
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

Lazy Take? #539

Open
treeowl opened this issue Sep 21, 2022 · 4 comments
Open

Lazy Take? #539

treeowl opened this issue Sep 21, 2022 · 4 comments
Labels

Comments

@treeowl
Copy link

treeowl commented Sep 21, 2022

The following type family is often useful:

type LazyTake :: Nat -> [k] -> [k]
type family LazyTake n xs where
  LazyTake 0 _ = '[]
  LazyTake n xs = Head xs ': LazyTake (n - 1) (Tail xs)

This is a promoted version of the (partial) function

lazyTake :: Natural -> [a] -> [a]
lazyTake 0 _ = []
lazyTake n xs = head xs : lazyTake (n - 1) (tail xs)

It's useful in constructions like

type LengthAtLeast n xs = xs ~ LazyTake n xs ++ Drop n xs

I imagine this package is not the one such a thing belongs in; any guess where I might find it, or where it might fit?

@RyanGlScott
Copy link
Collaborator

I'm not aware of any package that provides this.

Out of curiosity, does defining this lazily actually make a difference in practice on the type level? I was under the impression that the order of evaluation in type family equations wasn't specified.

@treeowl
Copy link
Author

treeowl commented Sep 21, 2022

Oh yes, definitely. The reduction semantics are not fully specified:

type family Loop :: [Bool] where
  Loop = Loop

> :kind LazyTake 0 Loop
-- unspecified result

However, LazyTake will definitely make progress if its list argument is not looping but merely stuck, polymorphic, or ambiguous.

type family Stuck :: [Bool] where
> :kind! LazyTake 0 Stuck
'[]
> :kind! LazyTake 2 Stuck
'[Head Stuck, Head (Tail Stuck)]

If I'm not very much mistaken, TypeError applications are treated as stuck unless and until GHC concludes that type checking has failed, so

> :kind! LazyTake 0 (TypeError ...)
'[]
> :kind! Length (LazyTake 2 (TypeError ...))
2
> :kind! LazyTake 2 (TypeError ...)
<Uh oh some errors>

In practice, the irretrievably stuck and type error cases are rather dull; reduction will make progress but usually fail somewhere else. But the ambiguous and polymorphic cases can allow useful type information to flow.

@RyanGlScott
Copy link
Collaborator

I see, that is surprisingly nuanced.

To be honest, I'm not even sure if there is a Haskell library that provides a term-level lazyTake, let alone a type-level one, so I can't offer any suggestions on where to look.

@treeowl
Copy link
Author

treeowl commented Sep 22, 2022

Not everything useful at the type level is equally useful at the term level. I rarely want Take at the type level; I rarely, if ever, want lazyTake at the term level.

Disclosure: in what I'm actually working on, we're using inductive naturals instead of garbage ones (except at the edges of the API), so I wouldn't likely use a GHC Nat version anyway.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants