Join GitHub today
GitHub is home to over 28 million developers working together to host and review code, manage projects, and build software together.
Sign upUser-defined data types (and records) not comparable #774
Comments
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
mgold
Aug 27, 2014
Contributor
Hmm, Haskell doesn't have this either unless you define it yourself (and it makes you define equality first). I think it shouldn't be too hard to work out the semantics of comparing ADTs but I think it's worth bringing up on the mailing list.
|
Hmm, Haskell doesn't have this either unless you define it yourself (and it makes you define equality first). I think it shouldn't be too hard to work out the semantics of comparing ADTs but I think it's worth bringing up on the mailing list. |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
pchiusano
Aug 27, 2014
Fyi, Haskell has deriving, which gives you a sensible default:
data Woot = Foo | Bar | Baz Int deriving (Ord,Show)I find that 90% of the time this is all you want - I don't care what the order is, I just want it to be ordered so I can use it as a key in a Map, etc. Also Ord is a subclass of Eq, so you only need to define Ord (which implies Eq).
I can raise this on the list.
pchiusano
commented
Aug 27, 2014
|
Fyi, Haskell has data Woot = Foo | Bar | Baz Int deriving (Ord,Show)I find that 90% of the time this is all you want - I don't care what the order is, I just want it to be ordered so I can use it as a key in a I can raise this on the list. |
pchiusano
changed the title from
Algebraic data types not comparable
to
User-defined data types (and records) not comparable
Aug 28, 2014
evancz
added
the
request
label
Nov 26, 2014
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
jameshfisher
Dec 14, 2014
+1 -- I just ran into this. Specifically I get the error:
Looks like you want something comparable, but the only valid comparable
types are Int, Float, Char, String, lists, or tuples.
The reason for the limitation in Set and Dict is not because of the implementation of those containers, which look like they use an RB-tree implemented in Elm. It should be simple to add an explicit comparator to this.
The inability to use user-defined data types in core containers (Set, Dict) is a serious shortcoming. I suggest that, until Elm gets typeclasses, those containers should allow the programmer to pass an explicit record-based first-class typeclass, e.g.
type alias Ord a = { compare: a -> a -> Basics.Order }
empty' : Ord k -> Dict k v
empty = empty' { compare = compare }
update : k -> (Maybe v -> Maybe v) -> Dict k v -> Dict k v
-- previously update : comparable -> (Maybe v -> Maybe v) -> Dict comparable v -> Dict comparable v
...
This only introduces a new function empty', redefines empty to be semantically the same, and changes the types for update, insert, remove etc. I think everything type-correct using the old types would also be type-correct with these new types, so this change is backwards-compatible.
(We must assume that the programmer only uses a single Ord k instance per type k. This assumption is necessary for e.g. merge, which would have to choose between the compare functions for the left and right dictionaries.)
jameshfisher
commented
Dec 14, 2014
|
+1 -- I just ran into this. Specifically I get the error:
The reason for the limitation in The inability to use user-defined data types in core containers (Set, Dict) is a serious shortcoming. I suggest that, until Elm gets typeclasses, those containers should allow the programmer to pass an explicit record-based first-class typeclass, e.g.
This only introduces a new function (We must assume that the programmer only uses a single |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
evancz
Aug 5, 2015
Member
Closing in favor of #1008 which will track any issue along these lines and see the different use cases and discussions.
|
Closing in favor of #1008 which will track any issue along these lines and see the different use cases and discussions. |
evancz
closed this
Aug 5, 2015
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
rtfeldman
Sep 28, 2015
Member
We've run into this a lot with validation errors.
Specifically we want to enumerate all the fields on a form as a union type (e.g. type Field = Username | Password | Email), and then to represent validation errors as a Dict Field String so we can easily look up whether there is an error on a given field with things like Dict.get.
We work around this by using List (Field, String) as a fake Dict and then filter it a lot. You can see a public example of where we're doing this here: http://package.elm-lang.org/packages/NoRedInk/elm-rails/1.1.0/Rails-Decode - we'd definitely prefer if that function had the following signature:
errors : Dict String comparable -> Decoder (Dict comparable (List String))Right now we can't do that, because if we did we couldn't use union types for our fields.
|
We've run into this a lot with validation errors. Specifically we want to enumerate all the fields on a form as a union type (e.g. We work around this by using errors : Dict String comparable -> Decoder (Dict comparable (List String))Right now we can't do that, because if we did we couldn't use union types for our fields. |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
Fresheyeball
Oct 29, 2015
Fresheyeball
commented
Oct 29, 2015
|
|
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
garbas
Aug 8, 2016
couldn't this be solved using something like
module Days exposing (Day, compare)
data Day = Mon | Tue | Wed | Thu | Fri | Sat | Sun
toInt = Day -> Int
toInt day =
case day of
Mon -> 1
Tue -> 2
Wed -> 3
Thu -> 4
Fri -> 5
Sat -> 6
Sun -> 7
compare = Day -> Day -> Bool
compare day1 day2 = (toInt day) > (toInt day2)
garbas
commented
Aug 8, 2016
|
couldn't this be solved using something like module Days exposing (Day, compare)
data Day = Mon | Tue | Wed | Thu | Fri | Sat | Sun
toInt = Day -> Int
toInt day =
case day of
Mon -> 1
Tue -> 2
Wed -> 3
Thu -> 4
Fri -> 5
Sat -> 6
Sun -> 7
compare = Day -> Day -> Bool
compare day1 day2 = (toInt day) > (toInt day2)
|
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
jvoigtlaender
Aug 8, 2016
Contributor
couldn't this be solved using something like
Depends on what you mean by "this". For example, one of the things mentioned in the opening post is the desire to be able to use Day as the key type in a Dict. Your proposal helps none with that.
Depends on what you mean by "this". For example, one of the things mentioned in the opening post is the desire to be able to use |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
garbas
Aug 8, 2016
@jvoigtlaender sry, i thought Dict part is self explanatory, to use it in Dict i would do, eg:
week = Dict.fromList[(toInt Mon, [ Event "meeting1" ], ...)
modayEvents = week.get <| toInt Monyou might even create a WeekDict module which would hide all the repetative toInt parts
garbas
commented
Aug 8, 2016
•
|
@jvoigtlaender sry, i thought Dict part is self explanatory, to use it in Dict i would do, eg: week = Dict.fromList[(toInt Mon, [ Event "meeting1" ], ...)
modayEvents = week.get <| toInt Monyou might even create a |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
jvoigtlaender
Aug 8, 2016
Contributor
In that plain form, what about the type correctness that the original poster wanted here? How is your solution better than one using Strings? How do you prevent someone from accidentally querying entry 8 in that dict? A Dict Day Whatever would have type guarantees that neither the String nor the toInt approach has.
About a less plain approach, somehow creating a WeekDict that hides toInt calls: How are you going to apply all the wonderful functions from http://package.elm-lang.org/packages/elm-lang/core/latest/Dict and http://package.elm-lang.org/packages/elm-community/dict-extra/latest/Dict-Extra on your WeekDict encapsulation?
|
In that plain form, what about the type correctness that the original poster wanted here? How is your solution better than one using About a less plain approach, somehow creating a |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
garbas
Aug 9, 2016
@jvoigtlaender thank you for explaining it to me, i'm new to elm. i see now what do you mean by type guarantee.
garbas
commented
Aug 9, 2016
|
@jvoigtlaender thank you for explaining it to me, i'm new to elm. i see now what do you mean by type guarantee. |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
OvermindDL1
Aug 9, 2016
About a less plain approach, somehow creating a WeekDict that hides toInt calls: How are you going to apply all the wonderful functions from http://package.elm-lang.org/packages/elm-lang/core/latest/Dict and http://package.elm-lang.org/packages/elm-community/dict-extra/latest/Dict-Extra on your WeekDict encapsulation?
Which can be solved once Elm gets HKT's. Or those libraries decide to invert function control (which I doubt they would do, nor should they really do).
OvermindDL1
commented
Aug 9, 2016
Which can be solved once Elm gets HKT's. Or those libraries decide to invert function control (which I doubt they would do, nor should they really do). |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
|
Relevant to this discussion: https://github.com/eeue56/elm-all-dict |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
OvermindDL1
Aug 10, 2016
@rtfeldman Ooo interesting find, though it might be better to make toString wrappers in your own code? I am curious if there is a Dict that can store any record especially though. I'd love to be able to store dynamic TEA component models like that in a library I've made...
OvermindDL1
commented
Aug 10, 2016
|
@rtfeldman Ooo interesting find, though it might be better to make toString wrappers in your own code? I am curious if there is a Dict that can store any record especially though. I'd love to be able to store dynamic TEA component models like that in a library I've made... |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
amigalemming
Sep 29, 2016
A type-safe but cumbersome work-around could be to create custom Dict type for Day keys, by wrapping a Dict Int a. E.g.
type DayDict a = DayDict (Dict Int a)
insert : Day -> a -> DayDict a -> DayDict a
insert day a (DayDict dict) = DayDict (Dict.insert (dayToInt day) a dict)
amigalemming
commented
Sep 29, 2016
•
|
A type-safe but cumbersome work-around could be to create custom
|
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
aryairani
Oct 8, 2016
Running into this. If tuples are comparable, shouldn't records (which are also an enumerable list of fields) and ADTs (which are are an enumerable list of constructors, each with an enumerable list of fields) also be comparable?
I also just had to downgrade my intended Sets and Dicts to Lists and Lists of pairs, and was unconsciously moving towards @rtfeldman's approach:
We work around this by using List (Field, String) as a fake Dict and then filter it a lot.
but I feel like this will get out of control quickly.
+1 to comparable ADTs, and why not records as well, especially since they may be part of the ADT.
aryairani
commented
Oct 8, 2016
•
|
Running into this. If tuples are comparable, shouldn't records (which are also an enumerable list of fields) and ADTs (which are are an enumerable list of constructors, each with an enumerable list of fields) also be comparable? I also just had to downgrade my intended Sets and Dicts to Lists and Lists of pairs, and was unconsciously moving towards @rtfeldman's approach:
but I feel like this will get out of control quickly. +1 to comparable ADTs, and why not records as well, especially since they may be part of the ADT. |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
brianhempel
Jan 23, 2017
Contributor
Sketch-n-Sketch would benefit from allowing arbitrary ADTs as set members and dictionary keys; also allowing arbitrary ADTs to be comparable would be beneficial as well.
A proposal. Perhaps decomposing comparability into two classes is clearest:
(1) For enumerated ADTs that have a natural ordering, such as the days of the week example above, allow the ADT to be annotated as comparable:
comparable type Day = Sun | Mon | Tue | Wed | Thu | Fri | SatIn practice, this would be equivalent to Haskell's deriving (Ord), and would allow the user to use < > >= <= on instances of their ADT.
(Ideally, a custom comparison function could be specified, but that is a separate feature.)
(2) It may not make sense to allow the use of < > >= <= on all ADTs. Some ADTs do not have a natural ordering. However, even these ADTs should be usable as set members or dictionary keys.
Requiring "comparability" to be a set member/dictionary key is an implementation detail—if sets were based on hashmaps then they would require "hashability". Because "comparability" is an implementation detail, keep it hidden.
Allow all ADTs to be internally comparable using special methods (e.g. internal_less_than, internal_greater_than) and silently use those methods to implement set membership and dictionary keying.
A user can thus use any ADT in a set or dictionary, but cannot call < > >= <= on their ADT unless they explicitly mark it comparable.
Update 2017-12-13: Having mulled on this for almost a year now, item (2) above is much more important to us than item (1). I can't think of any place in Sketch-n-Sketch where we need ADTs to be comparable so we can sort them: we just need to be able to use arbitrary items as set members and (less often) dictionary keys.
Update 2017-12-20: Item (2) of this proposal now has a pull request which allows using ADTs and records in sets.
|
Sketch-n-Sketch would benefit from allowing arbitrary ADTs as set members and dictionary keys; also allowing arbitrary ADTs to be comparable would be beneficial as well. A proposal. Perhaps decomposing comparability into two classes is clearest: (1) For enumerated ADTs that have a natural ordering, such as the days of the week example above, allow the ADT to be annotated as comparable: comparable type Day = Sun | Mon | Tue | Wed | Thu | Fri | SatIn practice, this would be equivalent to Haskell's (Ideally, a custom comparison function could be specified, but that is a separate feature.) (2) It may not make sense to allow the use of Requiring "comparability" to be a set member/dictionary key is an implementation detail—if sets were based on hashmaps then they would require "hashability". Because "comparability" is an implementation detail, keep it hidden. Allow all ADTs to be internally comparable using special methods (e.g. A user can thus use any ADT in a set or dictionary, but cannot call Update 2017-12-13: Having mulled on this for almost a year now, item (2) above is much more important to us than item (1). I can't think of any place in Sketch-n-Sketch where we need ADTs to be comparable so we can sort them: we just need to be able to use arbitrary items as set members and (less often) dictionary keys. Update 2017-12-20: Item (2) of this proposal now has a pull request which allows using ADTs and records in sets. |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
ktonon
Feb 9, 2017
I'm generating an elm package for the AWS SDK (from the apis/*.normal.json files). Some of the types are maps from enums to other things. For example, from devicefarm-2015-06-23.normal.json
"PurchasedDevicesMap":{
"type":"map",
"key":{"shape":"DevicePlatform"},
"value":{"shape":"Integer"}
},
"DevicePlatform":{
"type":"string",
"enum":[
"ANDROID",
"IOS"
]
},I would like to represent DevicePlatform as
type DevicePlatform
= ANDROID
| IOSbut currently I can't because then PurchasedDevicesMap becomes
Dict DevicePlatform Int
ktonon
commented
Feb 9, 2017
•
|
I'm generating an elm package for the AWS SDK (from the apis/*.normal.json files). Some of the types are "PurchasedDevicesMap":{
"type":"map",
"key":{"shape":"DevicePlatform"},
"value":{"shape":"Integer"}
},
"DevicePlatform":{
"type":"string",
"enum":[
"ANDROID",
"IOS"
]
},I would like to represent type DevicePlatform
= ANDROID
| IOSbut currently I can't because then Dict DevicePlatform Int |
adrianroe
referenced this issue
Feb 25, 2017
Closed
Unable to generalize a type variable. It is not unranked. #1569
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
masklinn
Nov 27, 2017
(1) For enumerated ADTs that have a natural ordering, such as the days of the week example above
I think the ability to have ordering on sum types in Elm would be very convenient (especially if it were necessarily opt-in would be neat). That said I want to note that DoW is a terrible example for it as the ordering is highly locale-dependent, as those paying attention may have noticed from @garbas 's comment:
- although the abrahamic tradition starts the week on Sunday, that only remains in the Americas, and a few other specific countries (not necessarily of abrahamic tradition): Japan, India, Israel, South Africa, Taiwan
- Most of the world (Europe, Oceania, most of Asia) starts the week on Monday, that's also the ISO-8601 standard
- And the Islamic world starts the week on Saturday
So weekdays are mostly an argument towards not ordering sum types.
Some ADTs do not have a natural ordering. However, even these ADTs should be usable as set members or dictionary keys.
To the extent that they are either "empty" or containing suitable items themselves, in fact that's one issue with a comparable annotation (aka deriving(Ord)), it would/will need to handle sum types which can not be made comparable.
Because "comparability" is an implementation detail, keep it hidden.
It's not an implementation detail, it's necessarily part of the API since it's a contract the map key must fulfill for the collection to work properly. Just as hashability (and equatability, and that the two are coherent) is part of the contract for hashmaps.
Adding a "secret" implicit comparability for hashmap would be unlikely to make things clearer, as you'd now get sum types which can and can't be used as map keys without any explanation why e.g. sum types where any constructor has collection or record associated data. Requiring an explicit annotation and only allowing annotated sum types to be used as keys would provide the occasion for the compiler to generate clear error messages in case of incompatibility.
masklinn
commented
Nov 27, 2017
•
I think the ability to have ordering on sum types in Elm would be very convenient (especially if it were necessarily opt-in would be neat). That said I want to note that DoW is a terrible example for it as the ordering is highly locale-dependent, as those paying attention may have noticed from @garbas 's comment:
So weekdays are mostly an argument towards not ordering sum types.
To the extent that they are either "empty" or containing suitable items themselves, in fact that's one issue with a
It's not an implementation detail, it's necessarily part of the API since it's a contract the map key must fulfill for the collection to work properly. Just as hashability (and equatability, and that the two are coherent) is part of the contract for hashmaps. Adding a "secret" implicit comparability for hashmap would be unlikely to make things clearer, as you'd now get sum types which can and can't be used as map keys without any explanation why e.g. sum types where any constructor has collection or record associated data. Requiring an explicit annotation and only allowing annotated sum types to be used as keys would provide the occasion for the compiler to generate clear error messages in case of incompatibility. |
pchiusano commentedAug 27, 2014
If I define:
I cannot compare
Dayvalues with<or useDayas a key in aDict. This really discourages me from defining (and using) more precise types in my programs. I could use aStringinstead ofDay, but then I lose the typechecker's help when I accidentally do something nonsensical that makes sense for arbitrary strings, but not for days. :(This would be fine if there were a way to provide a custom comparable implementation for a data type, but there's no way to do that, either. This comes up a lot. It's very common to want to use some ADT as a key in a
Dict, or store some ADT in aSet.