I'm looking to implement monotonically increasing ...
# daft-dev
n
I'm looking to implement monotonically increasing id's, right now it's
Copy code
df = df._add_monotonically_increasing_id("id")
but I want to adapt it to be something along the lines of
Copy code
df = df.with_column("id").mid()
Would
.mid()
belong in
daft.expressions
or something new like
daft.functions
or something else entirely?
r
@Colin Ho did you work on monotonically increasing sequences recently? I thought I saw a related commit.
IMO two things (1) it’s a projection on the DataFrame to add a new column so it’s a DF operator (2) lets use a different name than “mid” which could be confused for a median/p50 especially if applied to a column expression like an agg 🙂
c
I did the original monotonically increasing id implementation, we implemented it as a dataframe method
It would be nice to have it also as an expression, this is what i was thinking of: https://github.com/Eventual-Inc/Daft/issues/3704
Reason why it was not originally developed as an expression is because we didn't, and still don't, have standalone expressions that don't have an underlying literal / column.
So if you did want it as an expression then we would need to support that
r
Nice, a “generator” expression is a cool idea!
c
Copy code
from pyspark.sql import functions as sf
spark.range(0, 10, 1, 2).select(sf.monotonically_increasing_id()).show()
this is how it works in spark
👍 1
r
I think “sequence” is the term of art here, and could eventually take parameters.
This is an interesting read. I’ll need to check the SQL standard for more background. https://www.postgresql.org/docs/current/sql-createsequence.html
k
I think the question I had was where the monotonically_increasing_id expression would live
as in would we want to put this into a daft.functions namespace?
This will be the first of many function-syntax expressions we build. eventually we’d like to port most of our col(“x”).func() to func(col(“x”))
r
Nice. I think daft.functions makes sense for a namespace in general. Others are builtins, routines, info_schema but those are a little arcane. For the port, do you intend to support both chaining and composition?
n
How would chaining and composition look like for an example use case? But yes, I am intending to support both,
k
I think we would want to generally want users to use composition other than for functions that are tied to the origin expression such as .alias("x") or .is_null(). Implementation-wise we can probably do this in a gradual manner, where we incrementally move our expressions and add deprecation warnings to the corresponding methods in
Expression
Thoughts on just keeping these expression functions in
daft.expression
? We already have col, lit, list, struct, interval, and coalesce in there
r
IMO I would prefer the functions in
daft.functions
rather than
daft.expression
so they are grouped, but I don't think there are issues with re-exporting? I believe we could easily support both composition and chaining automagically with some monkey patching. Here's an example, Nishant.
Copy code
col("my_str").lower()   # chaining

lower(col("my_str"))    # composed
Functional languages often support both, and I think deprecating all current function APIs might be jarring to customers.
Additionally, there are ways to do even regular function composition that are preferred over the
h(g(f(x)))
formulation. Scala has the
f andThen g andThen h
. Haskell has
h . g . f
. F# has a forward composition operator
f >> g >> h
that returns a composed function and something similar called the forward pipe operator (
|>
). Functional programmers prefer these formulations because of the lack of nesting. Nesting is a lot harder cognitively to track than juxtaposition.
Elixir uses
|>
and they have a nice doc @Nishant Bhakar https://elixirschool.com/en/lessons/basics/pipe_operator
❤️ 1
n
thanks, taking a look right now! i agree about continuing support for chaining
k
I'm a little hesitant on providing multiple ways to do the same thing. There are expressions at the moment that definitely do not make sense in chaining-form (e.g. if_else). That along with the expression type namespacing (e.g. col("x").str.length()) which we want to remove, means users will probably see some breaking changes in our API anyway
r
You might be conflating expressions with functions. Functions make sense in either composition/chaining, but not all expressions do. In your example,
if_else
is a control-flow expression and not a function.
When I say "function" I mean in the sense of a "strict scalar normal-form function"
k
What do you mean by normal-form?
Are you referring to only unary expressions?
r
No not expressions, but scalar functions. A normal strict function is one that evaluates all of its arguments before invoking the routine itself. Control-flow does not do this e.g. if-else, coalesce
k
Ah I see. What would you consider boolean and/or?
r
binary expressions
specifically operators*
Operators have different typing, resolution, and coercion rules than scalar functions. It's important to distinguish between the two in your IR.
I had strict/non-strict flipped .. it's been a while since I read SICP 😅 https://en.wikipedia.org/wiki/Evaluation_strategy
k
I'm admittedly not familiar with these concepts. We might want to move to a different thread though, this is perhaps off topic
👍 1
r
Feel free to send me a DM if you have any additional questions and we can work through examples.
❤️ 1
Just ran into an issue about control-flow vs scalar functions related to this thread. This shows how coalesce is an SQL special form (normal) with control flow, which when implemented as a scalar (strict) function, does unnecessary work. https://github.com/Eventual-Inc/Daft/issues/4069 This will be interesting to fix for anyone interested!
😮 1
😨 1