I think roc should consider including an "id" function within the builtins set. it's just something that comes up now and again and I've liked having it as a builtin in other languages
it's often useful when some operation is optional, eg:
list |> (if fwd then \a -> a else List.reverse)
#becomes
list |> (if fwd then id else List.reverse)
this was an intentional omission actually :big_smile:
I've found that in general if an identity function is desirable, it's usually a sign that an API is missing a function
if that exact code example is from real-world code, I'd be curious what the larger example looks like!
Okay, interesting.
It is from real code, I'm writing a function that searches through a string (a bit of AOC catchup), but it needs to be able to operate forwards and backwards. There are a few places i need to reverse something or not depending on which way we are searching
I've replaced the id function uses with this odd little utility
ifF : a, Bool, (a -> a) -> a
ifF = \input, cond, f -> if cond then f input else input
if you're up for sharing the whole code, I'd love to check it out!
Here is a big chunk of the code. I'm well aware it's messy and not pretty, but I was trying to solve it with the least iteration possible and make use of breaking out of walks to return early
numberNames = [
"zero",
"one",
"two",
"three",
"four",
"five",
"six",
"seven",
"eight",
"nine",
]
nameToNum = \str ->
when str is
"zero" -> 0
"one" -> 1
"two" -> 2
"three" -> 3
"four" -> 4
"five" -> 5
"six" -> 6
"seven" -> 7
"eight" -> 8
"nine" -> 9
_ -> crash "bad"
ifF : a, Bool, (a -> a) -> a
ifF = \inp, cond, f -> if cond then f inp else inp
findFirstNum : List U8, [Forward, Backward] -> U32
findFirstNum = \str, forward ->
rev = forward != Forward
# reverse if needed
names : List List U8
names =
numberNames
|> List.map \name ->
name
|> Str.toUtf8
|> ifF rev List.reverse
walker = if rev then List.walkBackwardsUntil else List.walkUntil
finders =
names
|> List.map \name ->
name
|> WordFinder.fromList
|> WordFinder.startSearch str
num =
str
|> walker
{ finders, idx: 0, num: 0 }
\state, char ->
# BookKepping so we have an index
idx = state.idx
nextState = { state & idx: idx + 1 }
if char >= 0x30 && char <= 0x39 then
Break { state & num: (char - 0x30) }
else
# step all our finders forward one
nextFinders =
state.finders
|> List.map \finder ->
WordFinder.nextStep finder char idx
# see if any matched
matched =
nextFinders
|> List.walkUntil (Err NotFound) \state2, finder ->
when WordFinder.firstMatch finder is
Ok _start -> Break (Ok (finder |> WordFinder.searchingFor))
_ -> Continue state2
# If we got a match we can return early
when matched is
Ok match ->
matchNum = match |> ifF rev List.reverse |> Str.fromUtf8 |> Result.withDefault ("") |> nameToNum
Break ({ state & num: matchNum })
_ -> Continue { nextState & finders: nextFinders }
|> .num
num |> Num.toU32
getPair = \inp ->
inp
|> List.map \str ->
strList =
str
|> Str.toUtf8
(strList |> findFirstNum Forward, strList |> findFirstNum Backward)
pairToNum = \(a, b) ->
(a * 10 + b)
parse = \str ->
str
|> Str.split "\n"
|> getPair
|> List.map pairToNum
I have a whole other chunk of code that implements a simple "wordfinder" that just steps along looking for matches for a specific string and keeps track of any partial matches as it goes.
but i need to iterate through both forward and backwards, so the words I'm matching and the the output needs to be reversed
ah! So considering ifF
is always passed List.reverse
, personally I'd write this function:
reverseIf : List a, Bool -> List a
reverseIf = \list, shouldReverse ->
if shouldReverse then list else List.reverse list
then the two call sites would look like this:
name
|> Str.toUtf8
|> reverseIf rev
matchNum = match |> reverseIf rev |> Str.fromUtf8 |> Result.withDefault ("") |> nameToNum
I like how self-descriptive |> reverseIf rev
is :smiley:
That's fair, I suppose in almost any case you would want an Id function you could probably make a more descriptive wrapper.
I will take a look through some of my other functional code and see where I've used the "Id" function to see if there are any other use cases that would warrant it more.
I do think mostly it would be cases where you are returning one of a few different transformation functions and you sometimes want to just do nothing
I've used Result.keepOks \a -> a
pretty frequently
That feels like a case of maybe bad naming on our part. Cause it really should be List.mapAndKeepOks
with a separate List.keepOks
As a note on that, I'm a big fan of using the name List.choose for that functionality
I guess I certainly think that mapAndKeep crosses a line of verbosity
Yeah, I don't think mapAndKeep
is a good name it is just being honest about functionality. I think a more common name is filterMap
, right?
yeah it's basically filterMap except it seemed weird to have one for Ok
but not Err
since we have keepIf
and dropIf
also for autocomplete discoverability there's an argument for having it start with List.map____
so if you start typing that it comes up in autocomplete
a lot of Roc function names are designed with autocomplete discoverability in mind
Is performance the only reason not to have it as a separate List.map |> List.keepOks
with keepOks
being literal and essentially defaulting to the identity version?
Long term if we had automatic under the hood iterators would that fix the perf as well
That's pretty compelling tbh.
Maybe just "mapKeep"
Would it be possible to go fully generic and make a function like List.keep Ok mylist
?
That only keeps items with that tag and returns the tag's content?
See a pattern that's come up a few times for me is
mylist= [TagA 1, TagB "hi", ..etc]
mylist|> List.KeepOks \ a ->
when a is
TagA a -> Ok a
_-> Err {}
And that fuction would make it way easier
(Wrote this on mobile sorry if it's not quite right :sweat_smile:)
Something like that should work, but only if that tag contains only a single field
Hmm....though you can't pattern match on variables, so nvm
Yeah that was my thought, you guys don't have some "magic compiler functions" that would let you check if a variable is a specific tag and then dump out the contents of that tag?
This is probably a place where you either need reflection, some kind of inbuilt trickery, or macros
might not feel ergonomic, but one idea would be:
List.keepMap Result.isOk \elem -> ...
List.dropMap Result.isOk \elem -> ...
eh, but then you end up with all the Ok
wrappers still there
Or you have to write custom ismytag functions for other tags
Brendan Hansknecht said:
Is performance the only reason not to have it as a separate
List.map |> List.keepOks
withkeepOks
being literal and essentially defaulting to the identity version?
Long term if we had automatic under the hood iterators would that fix the perf as well
perf is one reason, but also it can be convenient when doing things like
List.filterMap strings Num.fromStr
Ah, for sure
Though if we had the working automatic iterators (again thinking long term) that would be equivalent to:
List.map strings Num.fromStr |> List.keepOks
Not much more verbose and theoretically would be same perf.
true!
Last updated: Jul 06 2025 at 12:14 UTC