Warn for type constructor aliases w/ suggestions#2932
Warn for type constructor aliases w/ suggestions#2932justinwoo wants to merge 1 commit intopurescript:masterfrom
Conversation
|
Could you explain what "simple" means? |
|
Does this also warn in situations where we're using the synonym arguments in multiple positions, or are applying parts of the aliased type constructor? For example in Halogen: type ParentHTML f g p m = HTML (ComponentSlot HTML g m p (f Unit)) (f Unit) |
|
I think |
|
Check should be applied after eta reduction. |
|
I'm not sure I agree that aliases for type constructors are always a bad thing. What if I just want to use a type synonym to make a name shorter? Or what if I just want to use a new naming convention without changing semantics? -- Maybe if I work with lots of Scala developers ...
type Option = Maybe@hdgarrood I don't think your example would be an issue here since |
|
Aren't all type constructor aliases confusing? I think any case of I hope to not warn on type applications though, even if it's as simple as |
|
Ah I see, thanks. Having thought about this a little more, and similar to what @paf31 said, I am a little uneasy about putting this into the compiler as a warning. I agree that this kind of type alias is almost always a bad idea, but also I'm not sure I'm comfortable having the compiler generate a warning about it in every case. |
|
I don't think we should try to prevent users from renaming things. |
|
I agree with @justinwoo. A type alias is not a rename because it does not create appropriate constructors and deconstructors. In practice, type aliases like this are used in places where a |
|
I'm also not a fan of |
|
Every type alias is just renaming, so by this logic, they're all bad, right? I would say lenses give a nice example where aliases are doing something worthwhile, but even then you have all of the same error message issues. I agree it's almost never a good practice to use aliases like this, but then what is a good use case for an alias? Can we characterize those? If not, I'd rather not add a warning for something so specific. Edit: following up on "they're all bad", I would actually seriously consider just removing type aliases completely 😄 |
In my opinion, a type alias is only useful for the sole case in which you are extracting type duplication from type declarations. i.e. it's the moral equivalent of using a let binding for an expression to avoid repeating the expression in multiple places. In this case the name is not useful, per se, it's almost irrelevant, the point of using the alias is to remove the duplication to make maintenance easier. All other uses of type aliases are design smells, and warning against them would be preferable to me in any code base I work on or oversee. I think you can detect this with relatively simple logic: a type alias that does not apply strictly more type parameters than it accepts is always bad. For example, |
I find that idea somewhat horrifying 😆. There are cases where having aliases improves readability of types hugely, reducing duplication or avoiding the need to write out entire complicated constructions. Even just I think the suggestion here is just |
|
I don't think it's possible to characterize bad aliases, but I agree that However, we should probably handle things like |
Definitely! I have a branch with a warning for "over applied" aliases that I did months ago, I'll spruce it up and get it open as a WIP at least. |
| xs -> " " ++ (intercalate " " $ T.unpack . fst <$> xs) | ||
| ctr = case ty of | ||
| TypeConstructor (Qualified _ (ProperName tn)) -> T.unpack tn | ||
| _ -> "" |
There was a problem hiding this comment.
Ah, yeah, definitely.
| where | ||
| name' = T.unpack name | ||
| args' = case args of | ||
| [] -> "" |
There was a problem hiding this comment.
In this section, I just want to print out an empty string if I don't have any type arguments to my type synonym. Should I rewrite this section in some way?
| CannotGetFileInfo{} -> "CannotGetFileInfo" | ||
| CannotReadFile{} -> "CannotReadFile" | ||
| CannotWriteFile{} -> "CannotWriteFile" | ||
| TypeConstructorAlias{} -> "TypeConstructorAlias" |
There was a problem hiding this comment.
After this is merged, can you please add docs to the docs repo for this error too?
There was a problem hiding this comment.
Yes, definitely needs some docs
src/Language/PureScript/Errors.hs
Outdated
| renderSimpleErrorMessage OverlappingNamesInLet = | ||
| line "The same name was used more than once in a let binding." | ||
| renderSimpleErrorMessage (TypeConstructorAlias _ _ _) = | ||
| line "The type synonym provided was an alias for a Type Constructor. These kinds of types can lead to confusing programs." |
There was a problem hiding this comment.
Can we say this?
This type synonym aliases a type constructor. Consider using a newtype instead.
"confusing programs" is a bit vague, and we can give examples in the docs repo instead.
There was a problem hiding this comment.
Perhaps "consider using a newtype or using the original type instead"?
|
Throwing my hat in. I'd be really interested to see how removal of type aliases played out. I feel like life would be better overall. It would push us to make using newtypes even easier to work with; they're already pretty easy to use with newtype deriving and |
|
I strongly disagree with removing type aliases entirely. They're an extremely useful tool in Haskell development. Removing them would make development with eg Servant or Lens totally untenable. It would require us to write I would be annoyed to see the original post's warnings from the compiler, mostly because it seems more like an appropriate thing for a linter tool to have as an option. I use type aliases like these somewhat frequently in development when I expect the type of some related group of functions to change, or if I'm not certain of it. For example: type Filter = Foo -> Bar -> Either [String] Quux
hasThing :: Filter
hasWhatever :: Filter
hasSomeOther :: Filter
-- repeat about 40 timesthen a change to the signature (eg, |
|
I think the warning is just to alias types like Number or String |
Don't worry, I don't think anyone is seriously considering it 😄
Now that you bring it up, having this as a linter option makes a lot of sense. The question is, should all linter options be baked into the compiler as warnings? Without the ability to filter them on the editor side, I think probably not. |
|
@nwolverson's VSCode/Atom editor extensions let you exclude specific warnings at-will, and I've used this to not warn on inferred wildcard and function signatures for my tutorial videos to not have too much noise, e.g. https://twitter.com/jusrin00/status/850375735864950784 Probably someone should make some easy way for IDE server clients to get this list of warnings?... |
|
I'm not in favor of this as a warning. Which programs are confusing? If we preserved aliases after desugaring, would they still be confusing? What bugs would be prevented by following this warning? |
|
I feel like this strongly implies we should also warn for aliases at the value level. Maybe someone wants to preserve backwards compatibility under a rename? |
Very good point. This is the strongest argument against this warning I've seen so far, I think. |
|
If we preserved synonyms after desugaring, which is a longstanding issue, then the errors could probably be far less confusing, and I think this warning would be moot: |
That won't work because you won't get constructors / deconstructors just from a type alias. In any case, you're arguing to avoid making something a warning to allow teams to increase technical debt. I'd say that's an excellent argument for making it a warning. 😉 |
d0456af to
c5524f1
Compare
|
The whole point of the technical debt metaphor is that it's sometimes reasonable to take out a loan as long as you pay it off later. |
c5524f1 to
7f449d0
Compare
|
Aren't all type operator aliases just as bad as these sorts of aliases? Edit: I'll elaborate my thinking here. Why do we use infix operator aliases for type constructors like As another example, maybe in my UI library, I want to define a synonym |
|
I don't think so - operators don't attempt to convey meaning, the way |
|
Maybe this is controversial enough that we don't do it though. Seems like there's not a consensus here, and it is the kind of thing a separate linter could deal with. It's not so much a "it's likely you're making a mistake"-level lint warning as it is a "that's a questionable choice". |
|
I agree that |
|
I'd be okay with shelving this for now if needed, but I'd like to really try polling more users somehow about this. Type aliasing constructors is a very common way I've shot my feet off in the past (and sometimes in the present/future), and it seems like something where 90%+ of the cases would benefit from this being a warning. Maybe needs a "maybe later" tag or something 😛 |
|
@justinwoo Could you please create an issue? |
This is takes 0 type parameters and applies 6, so would be valid under my proposed rule for bad type aliases. Applying more than you require is a sign you're doing work, i.e. reducing duplication. Applying less than or the same as what you require is a sign you're doing no work, i.e. it's purely "semantic" change with no actual benefit to reducing duplication. |
|
Closing as this needs an issue to discuss, but let's reopen this if we come to a decision. Thanks! |
This is a PR for the slightly crazy idea I had to ban simple* type aliases. I chose to just make a warning for now with a suggestion that writes out a newtype form.
* In this case just meaning "is a type constructor on its own"
In screenshots: