-
Notifications
You must be signed in to change notification settings - Fork 324
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Method return type checks avoid conversion when the type fits #10882
Conversation
The original Haskell program provided by @radeusgd: class Connection a where
get_tables :: a -> [String]
data Snowflake_Connection = Snowflake_Connection_Impl -- here we'd have internals of the connection
instance Connection Snowflake_Connection where
get_tables c = ["some Snowflake table"] -- we'd call JDBC here ofc.
get_warehouses :: Snowflake_Connection -> [String]
get_warehouses c = ["SNOWFLAKE_WAREHOUSE"]
database_connect :: Connection c => Connection_Details d c => d -> c
database_connect details = make_connection_from_details details
class Connection_Details d c where
make_connection_from_details :: d -> c
data Snowflake_Connection_Details = Snowflake_Connection_Details String String String -- username, password etc.
instance Connection_Details Snowflake_Connection_Details Snowflake_Connection where
make_connection_from_details details = Snowflake_Connection_Impl
main :: IO ()
main = do
let snowflake_connection = database_connect (Snowflake_Connection_Details "foo" "bar" "baz")
print (get_tables snowflake_connection)
print (get_warehouses snowflake_connection) The program rewritten to Enso is made part of our test suite. |
Just a recap how one is supposed to write type classes in Enso. Define API and "Dictionary"Here is a way to define Set type class: from Standard.Base import Error
from Standard.Base.Errors.Common import Type_Error
from Standard.Base.Errors import Illegal_State
type Set a
Value o s:a
is_empty self = self.o.is_empty self.s
contains self e = self.o.contains self.s e
length self = self.o.length self.s
insert self e = Set.Value self.o <| self.o.insert self.s e
union self (other:Set) = if self.o != other.o then Error.throw (Type_Error "Incompatible sets") else
Set.Value self.o <| self.o.union self.s other.s
type Operator a
is_empty (_ : Set) = Error.Throw (Illegal_State.Error "is_empty not implemented")
contains (_ : Set) _ = Error.Throw (Illegal_State.Error "contains not implemented")
length (_ : Set) _ = Error.Throw (Illegal_State.Error "length not implemented")
insert (_ : Set) _ = Error.Throw (Illegal_State.Error "insert not implemented")
union (_ : Set) _ = Error.Throw (Illegal_State.Error "union not implemented") The Implementation with
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's really cool that we are able to implement this example in Enso.
But I'm actually worried about integrating this (I know I gave you the example, but this was just a way to show that this kind of type refinement is doable in Java or Haskell, I did not intent to argue that Enso should do it the same way).
We will probably want some way to be able to nicely typecheck Database.connect
to return 'more specific' types that expose additional methods, like Snowflake_Connection.warehouses
.
However, doing it this way is actually breaking the assumptions I made in #9812. I'm assuming that if I have a variable x : Integer
, it will only contain methods available on Integer
(and supertypes). Only if I have a variable of type a : Any
I assume that this can be 'anything' and allow calling any kind of methods on it.
If now we allow x : Integer
to actually be x : Integer & Mixin
and allow methods of Mixin
type to be called on x
, then no static type checking of method calls can be done - we are becoming fully dynamic as everywhere I can have random methods being added to types by such mixins.
This PR was supposed to solve the typechecking of Database.connect
, but it seems to me that the 'cost' of solving that problem is completely disabling us from doing static type analysis. At this point, correct typechecking of Database.connect
has no longer much benefit, as we are unable to do any typechecking at all. The only benefit is that we can 'correctly' typecheck this at runtime - but at runtime we know the concrete type of the object anyway so it is of little gain.
Please don't take this as criticism of the overall idea - I'm really glad we were able to implement this extension of the 'typeclass' mechanims in Enso with such a relatively small change. But I'm actually worried that this will cause us more problems than benefit.
Of course there is always possibility that I misunderstood something. If I'm wrong about preventing to do static type analysis, please let's discuss - I'll be happy to be shown that we can actually still do type analysis with such change. But I just don't see such a possibility.
As for the typing of For now, I think it is just safer to keep |
Hmm, I tried compiling an example:
and I see it is more nuanced than I thought - I thought the example will be troubling, sometimes calling the I'm still not entirely convinced why the |
Thinking through implications for #9812 - my static type analysis logic relies on And if on such But with this PR, only |
Btw. is it expected that
prints
? I thought that |
That's how OOP works and that's how it works in Java compiler or IDEs. If
I can understand why you think issue no. 1 complicates your work on static type checking. I assume if we could get no. 2 and allow only methods of |
Yes, the difference is unfortunate. I don't need this behavior on regular methods. It could be disabled. The motivating example only needs it for conversion methods. I need |
In any case: at current state this PR cannot be integrated. Closing. |
Implementation of **type checks** for **intersection types**. The idea is to split the list of `types[]` in `EnsoMultiValue` into two parts: - first `methodDispatchTypes` represent the types the value _"has been cast to"_ - the rest of the types represent the types the value _"can be cast to"_ By performing this separation we address the #10882 requirements. After a type check only methods available on the `methodDispatchTypes` can be invoked. However the value can still be cast to all the possible types.
Implementation of **type checks** for **intersection types**. The idea is to split the list of `types[]` in `EnsoMultiValue` into two parts: - first `methodDispatchTypes` represent the types the value _"has been cast to"_ - the rest of the types represent the types the value _"can be cast to"_ By performing this separation we address the #10882 requirements. After a type check only methods available on the `methodDispatchTypes` can be invoked. However the value can still be cast to all the possible types. (cherry picked from commit 2964457)
Pull Request Description
Discussion about type classes revealed that Enso is able to support Haskell 8 type classes with a single variable. However more complicated type classes (enabled with
-XMultiParamTypeClasses
and by default on in Haskell 9) are more complicated. Turns out one may do it at the end, but only with intersection types returned from functions and conversion methods. This PR enables such behavior.Related
Checklist
Please ensure that the following checklist has been satisfied before submitting the PR:
Scala,
Java,