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

Add head and tail builtins #2882

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open

Conversation

reegnz
Copy link

@reegnz reegnz commented Sep 7, 2023

head:

  • returns a single value or empty if input is a list
  • returns a string or empty if input is a string
  • fails if input is neither list or string

tail:

  • returns a list if input is a list
  • returns a string if input is a string
  • fails if input is neither list or string

@reegnz
Copy link
Author

reegnz commented Sep 7, 2023

These primitives helped me in implementing some convenience functions on top of them: https://github.com/reegnz/dotfiles/tree/master/jq

Most notably converting to/from camelcase.

Capitalizing strings is also easier to implement with such primitive filters exposed by jq.

head:
* returns a single value or empty if input is a list
* returns a string or empty if input is a string
* fails if input is neither list or string

tail:
* returns a list if input is a list
* returns a string if input is a string
* fails if input is neither list or string
@reegnz
Copy link
Author

reegnz commented Sep 7, 2023

Alternatively, one could use these as heads and tails (more cryptic, but gets the job done:

For lists:

[ .[:1][], .[1:]]

For strings:

[.[:1], .[1:]]

The unwrapped head for lists is inspired by other languages usually called destructuring, eg.:
https://elixir-lang.org/getting-started/pattern-matching.html
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment
https://www.typescriptlang.org/docs/handbook/variable-declarations.html#destructuring

[ "a", "bc" ]

[head, tail]
[ "a" ]

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test is failing

Expected ["a",""], but got ["a",[]] 

@@ -169,6 +169,8 @@ def nth($n; g):
else label $out | foreach g as $item ($n + 1; . - 1; if . <= 0 then $item, break $out else empty end) end;
def first: .[0];
def last: .[-1];
def head: select(length >0) | .[:1] | if type == "array" then .[] end;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Based on the tests, it seems more accurate to think of these functions as
firstItem and remainingItems.
Calling them head and tail seems misleading because it implies they would work like the equivalent shell commands that read the start and end of a file for n lines.
Instead, head always returns one item, while tail returns all except the first item.

I'm not confident these functions (in their current state) would be useful as builtins for the community as a whole. They seem like more of a niche thing that could exist in an independent module as seems to be the current case.

Copy link
Member

@wader wader May 31, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As @reegnz references in #2882 (comment) the head and tail terminology comes from pattern matching in function languages.

[ 1 ]
[ 1, [] ]

[head, tail]

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given it's possible for head to return empty values, it's probably better to write tests that check these functions independently. Otherwise, in tests like this, it's not obvious which function returned an empty value.

@pkoppstein
Copy link
Contributor

Sorry to be so negative, but I don't see the point of these additions, and indeed based on my present understanding, they seem to just add bloat at best and confusion at worst (confusion, because of the history of car and cdr; see also @jadutter's remarks).

Perhaps I'm missing something? Please feel free to enlighten me :-)

@reegnz
Copy link
Author

reegnz commented Jun 3, 2024

I don't mind the specific terminology used. Can be head and tail, or first and rest or even firstItem vs remainingItems (although this one would diverge most from existing naming in other languages).

Calling it bloat sounds funny to me. It's true, most functional languages are complex because they have stdlib-s with higher order abstractions shipping with them. Wouldn't call that bloat though, that's a pretty big stretch.

Just please give this the benefit of the doubt instead of straight up calling it bloat and dismissing it. People do make use of taking the first item and remaining items of a list.

An alternative to filters is to make destructuring a language-level feature, like in javascript and clojure.

@reegnz
Copy link
Author

reegnz commented Jun 3, 2024

BTW original issue I've opened was #2260 this was just a quick run at the problem, I don't mind how it's solved.

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

Successfully merging this pull request may close these issues.

None yet

5 participants