Conversation
|
@paf31 Is this just random syntax that happens to be the same as constraints, or pointing to a future extension of the type system? |
|
No, it's a new type level string. They will have kind We could reasonably add a library for working with type-level strings. A good starting point would be to have the compiler derive a class like class KnownSymbol (sym :: Symbol) where
knownSymbol :: Proxy sym -> Stringand toKnownSymbol :: forall r. String -> (forall sym. KnownSymbol sym => Proxy sym -> r) -> r |
Please consider full-on singleton types for all literal values. 🙏
👍 Awesome. Looks like a path toward first-classing field names in row types, yes? That'll buy us lenses which are polymorphic in the field name, among other miraculous things. |
|
Exactly. First-class labels are a much bigger change, but they'd definitely reuse this type machinery. |
|
I really like how little code is needed for this. Nifty! |
|
Are there cases where one would want to write a failing instance where one can't really come up with a default nonsense implementation? I guess you could just fill it in with coercions. |
|
Yeah, I think I'd use |
|
I think that only works if its behind a function guard, though. |
|
Oh I guess if you can't make an instance anyway, then it won't ever be created, and thus won't throw. |
|
@natefaubion Right, the creation is always guarded by a function which takes evidence for the |
|
Issues I see in priority order:
|
True, so perhaps
All instances need implementations (this is just a global rule, which makes a good default, but maybe not here), but we can give a very succinct empty implementation using the
Messages can be localized to the same extent than any other compile-time information can be. That is, via code generation.
This is based on the ICFP presentation, it's just a bit of a simplified version since we don't support symbol concatenation or symbols for types. We could add those, but this gives a better bang for our buck in the short term, I think. |
I think that's the reason it's a perfect example that should be accommodated.
Not if they're derived so I suppose these could be derivable?
But can they be formatted at all?
I figured just asking for clarification 👌 |
|
Maybe with constraint kinds you could add more information with a type annotation add _ _ = failWith (proxy :: Fail "Perhaps you meant append (<>)") |
|
Seems like it could address my first couple points! |
|
The sum of all constraints would have to be the same for both though. What would the instance look like? |
I'd much rather just make it simple to write the empty instances. |
|
You'd rather have to write arbitrary code that never gets run even though PS is already
I don't know but messages for a method/constrained function seem like a more common use case than generic missing instance. |
Sorry, I don't understand. You can't put the |
|
My thinking was that the instance always has a |
|
I see, but you're still going to struggle to write the instance. If I understand, you want to say something like "this has a |
|
Ok, this should be ready to review/merge now. |
|
I feel about this like your feedback about imports. It doesn't seem to address the user facing issue with a syntax that seems forced. Not only does it require an implementation that's never used but the closest thing we have to |
What's the user-facing issue you're referring to? |
^ The issue with missing instances is contextual to their function. Your original example exemplified that use case. I imagine |
I'm not sure what constraint level means, but method level isn't possible with the current type system. Can you give a concrete proposal for what you're describing? Would you be happier if it looked like instance noStringSemiring
:: Fail "String is not a Semiring. Perhaps you want (++) instead of (+)."
=> Semiring String where
add _ _ = failed
mult _ _ = failed
one = failed
zero = failedwhere I agree that ideally it would just be instance noStringSemiring
:: Fail "String is not a Semiring. Perhaps you want (++) instead of (+)."
=> Semiring Stringor something, but this won't be the final iteration of this feature. If it proves to be a success, we can go back and try to support that. Right now, it's overkill for what should be a relatively advanced feature, in my opinion. |
That's better but ostensibly the lesser issue if you plan to improve it later.
That's why I'm apprehensive. You suggested class Eq a where
eq :: a -> a -> BooleanWhat's characteristically better about
abs :: forall a. (Ord a, Ring a) => a -> a
abs x = if x >= zero then x else negate xBut if I tried to
A feature which aspires to provide library maintainers the capacity to contribute Elm-like compiler feedback based on monomorphic context though. |
|
Mulling over @natefaubion's suggestion but having no familiarity with dependent types it's strange the error message is parameterized on a single value versus a domain of values (e.g. nonempty strings)? |
|
do instances which have a Fail constraint show up in docs right now? If so, I think we should modify Language.PureScript.Docs.Convert.Single so that it discards them. |
|
@appshipit I get what you're saying about your intuition for |
|
Not criticizing |
|
Oh, I see what you're getting at now. Yeah, that's fair; I guess, for what you want, we'd need some kind of facility for some kind of pattern matching on of syntax trees, and supplying custom error messages for particular patterns, then (although I have no idea whether that is a good idea or even workable). I think that instances probably are the right target for custom error messages for certain type-level constructions, though (take the example in that ICFP video you linked, for instance). |
|
I think the instance is the right place to put the message, given why the error occurs in the first place. We get the error because |
|
On the contrary it's clearly the coincidence of |
There is no coincidence. Or rather, you can't have one without the other.
I've explained why that solution won't work with the current type system.
I'm not sure what you're getting at here. Can we nail down the issue here, or merge this? If there is a better solution which actually works with the type system, I've yet to see it. |
Seems like if we can't represent a solid solution through overloading the type system, we shouldn't overload the type system? |
|
As I say, I don't yet understand why this isn't a solid solution. With overlapping instances: class StringHint a
instance failForString :: Fail "Use (<>) for string concatenation instead of (+)" => StringHint String
instance succeedOtherwise :: StringHint a
class Semiring a where
add :: StringHint a => a -> a -> a
...That's the first two points covered, with a slightly more verbose implementation and possibly other confusing errors. Point is, it's a tool, and you can use it in different ways, with different trade-offs. |
|
What about a new syntax (or some kind of pragma) for doing this rather than trying to fit it into the type system syntax (could leverage the type system under the hood of course). Just be explicit about what is being expressed in the code rather than encoding it in another existing syntax that I don't believe is really designed to express this. Makes it easier for someone who comes across this syntax to look it up/understand it, and we can express exactly the thing we want to express w/o having to fill in boilerplate. |
|
Potentially, if we can come up with a concrete proposal. If the consensus is that this is not a good solution, I'm happy to leave it out of 0.9.1 or just close it entirely. Edit: I'd rather not add new syntax though, personally. I see this as being intimately linked with the type class system, so I don't see it as trying to shoehorn a feature into the type system. |
|
It would, of course, be nice to be able to tie programmable type errors to the use of individual functions, rather than the use of the instance as a whole, since it would permit more focused error messages (that is, error messages that can focus on the specific function which was used). However, I'm not sure it will be essential in practice. I say that for a couple of reasons. Most type classes do not have very many functions defined. I did a quick look at Now, that does mean that one would want to word an instance-level failure message carefully ... something like "If you were using I suppose that the user might find it odd that the compiler doesn't know whether you were trying to use The highlighted words above are meant to allude to a difference between this and what Elm does. The reason, I think, that Elm can more easily be more specific in tying errors to individual functions is that all of Elm's type-classes are built into the compiler itself, so the compiler has intimate, special-cased knowledge of exactly what they do. Here, the error messages are defined in Purescript code, and not special-cased, so we can use this mechanism for any old type-class. That's a huge win -- it is actually kind of miraculous. The other thing that occurs to me is that even where there are, say, 4 functions in a type-class, it will often be the case that only one of them is "tempting". In the case of All of this to say that I think the glass is 95% full here. |
I think empowering library maintainers to author error messages could leapfrog Elm in a way. I appreciate its simplicity. If there's any way to associate them with methods (or even non-class functions) I just believe it'd enable more relevant contextual feedback!
I'd like another few days to cogitate. We'll meet up this weekend anyway? |
|
Any in person discussion should be shared with the people here though, so no one's excluded for not being able to make it to Catalina. |
|
We have other things to finish for 0.9.1, so we can leave this here for a few days, but let's try to wrap it up by the end of the week so that we can finish off the core libraries. Lots of things will be blocked by that. |
|
@jbrownson suggested a syntax inspired by foreign function imports. @paf31 raised concerns related to implementation, performance, and potential not to accommodate future type system features. I wanted to present it to nurture other ideas that may find a compromise with existing type class machinery: -- would also work if add/append were defined monomorphically
error add :: forall a. String -> String -> a = "Use (++) to concatenate strings."
error append :: forall a. Number -> Number -> a = "Use (+) to add Numbers."
-- previous abs example
error abs :: forall a. Nat -> a = "Cannot abs a natural number because it implies negatability."
abs :: forall a. (Ord a, Ring a) => a -> a
abs x = if x >= zero then x else negate x |
|
Agreed with @rgrempel. I'd like to see this go in for 0.9.1. I don't think we are making any promises about backwards compatibility yet, and it's not as if people might start depending on this feature in a way that causes serious problems if we were to later remove it anyway. So if we ever do come up with an alternative that fully obsoletes it somehow, I don't see that there would be any real harm done. We can see from this PR that it's super simple from the point of view of compiler internals, too. I would love to see a more detailed proposal for an alternative solution; I don't think the ones offered so far are sufficiently fleshed-out to justify holding this back. In particular, it's not clear to me that any alternative solution couldn't complement what we have here instead of replacing it. Just for context, a slightly more advanced version of this feature just landed in GHC 8.0.1: https://downloads.haskell.org/~ghc/8.0.1/docs/html/users_guide/glasgow_exts.html#custom-errors with a great example of a custom error we could define in our Prelude too. This feature would also work great for types which cannot be given good Generic instances, such as JSDate and Map (as @paf31 recently pointed out in IRC). |
|
@AppShipIt From our conversation from last night: Generally, I like the idea, but I prefer the constraints approach for the following reasons:
I think your solution would be a better fit for languages without type classes and other type system features. |
I think this is right. We could mark this as experimental if we really wanted to, but I consider most things experimental while we're < 1.0 anyway. So I'll try to get this merged later today. |
|
I still want to discuss it further tonight or tomorrow. We could even fire up Zoom. No one has presented a use case this resolves other than incidentally. Error messages are arguably the biggest encumbrance in PureScript and improving them should be done considerately not with a type feature just because it's easy or neat or even precedented. Throwing it in without a plan to make it contextual to function application looks like YAGNI. The justification about breaking changes likewise didn't favor #1901. On the contrary your feedback was to explorer the underlying issue. If a set of use cases could be provided that don't depend on guessing which method was applied I'd be excited. |
|
After talking with @AppShipIt and @jbrownson some more about this over the weekend, I'm going to get this merged in. We discussed an alternative function-level approach, which can be tracked in a separate issue, and use cases for this feature. The |
|
To clarify for library authors instance-directed errors serve a different use case than function-directed errors. |
* Programmable type errors * update unifiesWith and typeHeadsAreEqual
Fix #1561.
This will allow to provide better type errors for things like
Stringnot being aSemiringorFunctionnot having decidable equality in Prelude.You can now write
and
"Hello" + "World"will give back