Hey!
Loved your presentation at Zurihac today! 🙌🏻 I run into the following issue while running the code:
dataframe-operations 1.0.1.1 fails to compile against dataframe-core 1.0.2.0 (ambiguous record
fields)
Summary
dataframe-core 1.0.2.0 (released 2026-06-06) breaks dataframe-operations 1.0.1.1,
and the existing bounds let cabal pick the broken combination. Anyone with a fresh
Hackage index who ends up with dataframe 2.1.0.0/2.1.0.1 (or any plan with
dataframe-operations < 1.1) hits ~28 Ambiguous occurrence errors in
DataFrame.Functions.
Error
[ 2 of 17] Compiling DataFrame.Functions ( src/DataFrame/Functions.hs, ... )
src/DataFrame/Functions.hs:46:22: error:
Ambiguous occurrence ‘unaryFn’
It could refer to
either ‘DataFrame.Internal.Expression.unaryFn’,
imported from ‘DataFrame.Internal.Expression’ at src/DataFrame/Functions.hs:16:1-36
or the field ‘unaryFn’ of record ‘UnUDF’,
imported from ‘DataFrame.Internal.Expression’ at src/DataFrame/Functions.hs:16:1-36
|
46 | Unary (MkUnaryOp{unaryFn = f, unaryName = "unaryUdf", unarySymbol = Nothing})
| ^^^^^^^
(…and the same for unaryName, unarySymbol, binaryFn, binaryName,
binarySymbol, binaryCommutative, binaryPrecedence at every
MkUnaryOp{…} / MkBinaryOp{…} construction site in DataFrame/Functions.hs.)
Root cause
dataframe-core 1.0.2.0 refactored DataFrame.Internal.Expression to introduce
open typeclasses UnaryOp / BinaryOp whose method names intentionally match
the UnUDF / BinUDF record fields (unaryFn, unaryName, unarySymbol,
binaryFn, …). The defining module compiles because it enables
NoFieldSelectors + DisambiguateRecordFields.
However, dataframe-operations 1.0.1.1's DataFrame.Functions imports
DataFrame.Internal.Expression unqualified and does not enable
DisambiguateRecordFields, so every record construction like
MkUnaryOp{unaryFn = f, …} now sees both the class method and the field and
fails as ambiguous.
The fix already exists in dataframe-operations 1.1.0.1 (released the same
day), and dataframe 2.1.0.2 correctly requires dataframe-operations ^>= 1.1.
The problem is the old releases remain installable in a broken configuration.
Why the solver picks the broken plan
dataframe-operations 1.0.1.1 declares dataframe-core ^>= 1.0, which wrongly
admits 1.0.2.0. There is no Hackage revision excluding it.
dataframe 2.1.0.0 / 2.1.0.1 declare dataframe-operations ^>= 1.0
(i.e. < 1.1), so any plan that selects those versions is forced onto the
broken ops + new core combination.
dataframe-hasktorch 0.2.0.0 declares dataframe-operations ^>= 1.0
(i.e. < 1.1). This makes the breakage unavoidable even without pinning:
a build-depends of dataframe, dataframe-hasktorch conflicts with
dataframe 2.1.0.2 (needs ops ^>= 1.1), so the solver backtracks to
dataframe 2.1.0.x + dataframe-operations 1.0.1.1 + dataframe-core 1.0.2.0
→ compile failure.
Reproduction
With a Hackage index from 2026-06-06 or later (GHC 9.6.7, cabal 3.14.1.1):
cabal install --lib "dataframe ==2.1.0.0"
# or, with no pin at all:
cabal install --lib dataframe dataframe-hasktorch
Both resolve to dataframe-core-1.0.2.0 + dataframe-operations-1.0.1.1 and
fail compiling DataFrame.Functions as above.
Suggested fixes
- Hackage metadata revision on
dataframe-operations 1.0.1.1 (and any other
< 1.1 release with the same bound): constrain dataframe-core < 1.0.2.
This alone removes every broken install plan.
- Same revision treatment for any other
dataframe-* sub-package that uses the
MkUnaryOp/MkBinaryOp record syntax with dataframe-core ^>= 1.0 and no
DisambiguateRecordFields.
dataframe-hasktorch: release a version allowing
dataframe-operations ^>= 1.1 so it composes with dataframe 2.1.0.2.
Workaround for users
Add an explicit pin on the old core, e.g.:
build-depends: dataframe ==2.1.0.0, dataframe-core ==1.0.1.1, dataframe-hasktorch ==0.2.0.0
or, when not using dataframe-hasktorch, simply allow the solver to pick
dataframe 2.1.0.2.
Versions involved
| Package |
Version |
Uploaded |
Status |
dataframe-core |
1.0.2.0 |
2026-06-06 |
introduces the class/field name overlap |
dataframe-operations |
1.0.1.1 |
(pre-existing) |
broken against core 1.0.2.0; bound too loose |
dataframe-operations |
1.1.0.1 |
2026-06-06 |
fixed |
dataframe |
2.1.0.0 / 2.1.0.1 |
2026-05-16 / — |
force ops < 1.1 → broken plans |
dataframe |
2.1.0.2 |
2026-06-06 |
fixed (requires ops ^>= 1.1) |
dataframe-hasktorch |
0.2.0.0 |
(latest) |
still requires ops < 1.1 → blocks fixed plans |
Hey!
Loved your presentation at Zurihac today! 🙌🏻 I run into the following issue while running the code:
dataframe-operations 1.0.1.1fails to compile againstdataframe-core 1.0.2.0(ambiguous recordfields)
Summary
dataframe-core 1.0.2.0(released 2026-06-06) breaksdataframe-operations 1.0.1.1,and the existing bounds let cabal pick the broken combination. Anyone with a fresh
Hackage index who ends up with
dataframe 2.1.0.0/2.1.0.1(or any plan withdataframe-operations < 1.1) hits ~28Ambiguous occurrenceerrors inDataFrame.Functions.Error
(…and the same for
unaryName,unarySymbol,binaryFn,binaryName,binarySymbol,binaryCommutative,binaryPrecedenceat everyMkUnaryOp{…}/MkBinaryOp{…}construction site inDataFrame/Functions.hs.)Root cause
dataframe-core 1.0.2.0refactoredDataFrame.Internal.Expressionto introduceopen typeclasses
UnaryOp/BinaryOpwhose method names intentionally matchthe
UnUDF/BinUDFrecord fields (unaryFn,unaryName,unarySymbol,binaryFn, …). The defining module compiles because it enablesNoFieldSelectors+DisambiguateRecordFields.However,
dataframe-operations 1.0.1.1'sDataFrame.FunctionsimportsDataFrame.Internal.Expressionunqualified and does not enableDisambiguateRecordFields, so every record construction likeMkUnaryOp{unaryFn = f, …}now sees both the class method and the field andfails as ambiguous.
The fix already exists in
dataframe-operations 1.1.0.1(released the sameday), and
dataframe 2.1.0.2correctly requiresdataframe-operations ^>= 1.1.The problem is the old releases remain installable in a broken configuration.
Why the solver picks the broken plan
dataframe-operations 1.0.1.1declaresdataframe-core ^>= 1.0, which wronglyadmits
1.0.2.0. There is no Hackage revision excluding it.dataframe 2.1.0.0/2.1.0.1declaredataframe-operations ^>= 1.0(i.e.
< 1.1), so any plan that selects those versions is forced onto thebroken ops + new core combination.
dataframe-hasktorch 0.2.0.0declaresdataframe-operations ^>= 1.0(i.e.
< 1.1). This makes the breakage unavoidable even without pinning:a build-depends of
dataframe, dataframe-hasktorchconflicts withdataframe 2.1.0.2(needs ops^>= 1.1), so the solver backtracks todataframe 2.1.0.x+dataframe-operations 1.0.1.1+dataframe-core 1.0.2.0→ compile failure.
Reproduction
With a Hackage index from 2026-06-06 or later (GHC 9.6.7, cabal 3.14.1.1):
Both resolve to
dataframe-core-1.0.2.0+dataframe-operations-1.0.1.1andfail compiling
DataFrame.Functionsas above.Suggested fixes
dataframe-operations 1.0.1.1(and any other< 1.1release with the same bound): constraindataframe-core < 1.0.2.This alone removes every broken install plan.
dataframe-*sub-package that uses theMkUnaryOp/MkBinaryOprecord syntax withdataframe-core ^>= 1.0and noDisambiguateRecordFields.dataframe-hasktorch: release a version allowingdataframe-operations ^>= 1.1so it composes withdataframe 2.1.0.2.Workaround for users
Add an explicit pin on the old core, e.g.:
or, when not using
dataframe-hasktorch, simply allow the solver to pickdataframe 2.1.0.2.Versions involved
dataframe-coredataframe-operationsdataframe-operationsdataframe< 1.1→ broken plansdataframe^>= 1.1)dataframe-hasktorch< 1.1→ blocks fixed plans