Say I have the following type:
newtype T1 a = T1 a
I can make a Show instance for it:
instance showT1Generic :: Show a => Show (T1 a) where
show (T1 a) = "generic: " <> show a
However, let's say I want to do something special for types T1 Int. I tried doing this:
instance showT1Int :: Show (T1 Int) where
show (T1 a) = "int: " <> show a
and it compiles, however running in psci doesn't work as expected:
> T1 'a'
generic: 'a'
> T1 1
generic: 1
Am I doing this wrong?
Defining both those instances will raise an OverlappingInstances warning, for the reason you're finding here - it can make the instance that is chosen unpredictable.
I think if you flip the order of the instance definitions this will work, but it's not really recommended to ignore the OverlappingInstances warning.
You can't really do what you're trying to do here - you could alter the behaviour of an instance with a newtype, but specialising instances for some concrete types and having a generic fallback is always going to result in overlapping instances as it stands. Maybe if we introduce instance chains this will become possible, but until then you're better off using monomorphic functions for this kind of thing.
Related
Consider the following simple snippet of PureScript code
a :: Int
a = 5
b :: Int
b = 7
c = a + b
main ∷ Effect Unit
main = do
logShow c
The program successfully infers the type of C to be Int, and outputs the expected result:
12
However, it also produces this warning:
No type declaration was provided for the top-level declaration of c.
It is good practice to provide type declarations as a form of documentation.
The inferred type of c was:
Int
in value declaration c
I find this confusing, since I would expect the Int type for C to be safely inferred. Like it often says in the docs, "why derive types when the compiler can do it for you?" This seems like a textbook example of the simplest and most basic type inference.
Is this warning expected? Is there a standard configuration that would suppress it?
Does this warning indicate that every variable should in fact be explicitly typed?
In most cases, and certainly in the simplest cases, the types can be inferred unambiguously, and indeed, in those cases type signatures are not necessary at all. This is why simpler languages, such as F#, Ocaml, or Elm, do not require type signatures.
But PureScript (and Haskell) has much more complicated cases too. Constrained types are one. Higher-rank types are another. It's a whole mess. Don't get me wrong, I love me some high-power type system, but the sad truth is, type inference works ambiguously with all of that stuff a lot of the time, and sometimes doesn't work at all.
In practice, even when type inference does work, it turns out that its results may be wildly different from what the developer intuitively expects, leading to very hard to debug issues. I mean, type errors in PureScript can be super vexing as it is, but imagine that happening across multiple top-level definitions, across multiple modules, even perhaps across multiple libraries. A nightmare!
So over the years a consensus has formed that overall it's better to have all the top-level definitions explicitly typed, even when it's super obvious. It makes the program much more understandable and puts constraints on the typechecker, providing it with "anchor points" of sorts, so it doesn't go wild.
But since it's not a hard requirement (most of the time), it's just a warning, not an error. You can ignore it if you wish, but do that at your own peril.
Now, another part of your question is whether every variable should be explicitly typed, - and the answer is "no".
As a rule, every top-level binding should be explicitly typed (and that's where you get a warning), but local bindings (i.e. let and where) don't have to, unless you need to clarify something that the compiler can't infer.
Moreover, in PureScript (and modern Haskell), local bindings are actually "monomorphised" - that's a fancy term basically meaning they can't be generic unless explicitly specified. This solves the problem of all the ambiguous type inference, while still working intuitively most of the time.
You can notice the difference with the following example:
f :: forall a b. Show a => Show b => a -> b -> String
f a b = s a <> s b
where
s x = show x
On the second line s a <> s b you get an error saying "Could not match type b with type a"
This happens because the where-bound function s has been monomorphised, - meaning it's not generic, - and its type has been inferred to be a -> String based on the s a usage. And this means that s b usage is ill-typed.
This can be fixed by giving s an explicit type signature:
f :: forall a b. Show a => Show b => a -> b -> String
f a b = s a <> s b
where
s :: forall x. Show x => x -> String
s x = show x
Now it's explicitly specified as generic, so it can be used with both a and b parameters.
I have a type class defined like this :
class Repo e ne | ne -> e, e -> ne where
eTable :: Table (Relation e)
And when I try to compile it I get this :
* Couldn't match type `Database.Selda.Generic.Rel
(GHC.Generics.Rep e0)'
with `Database.Selda.Generic.Rel (GHC.Generics.Rep e)'
Expected type: Table (Relation e)
Actual type: Table (Relation e0)
NB: `Database.Selda.Generic.Rel' is a type function, and may not be injective
The type variable `e0' is ambiguous
* In the ambiguity check for `eTable'
To defer the ambiguity check to use sites, enable AllowAmbiguousTypes
When checking the class method:
eTable :: forall e ne. Repo e ne => Table (Relation e)
In the class declaration for `Repo'
|
41 | eTable :: Table (Relation e)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
I was expecting everything to be unambiguous since I've explicitly stated that e determines ne and vice versa.
However, if I try to define my class like this just for the testing purposes, it compiles :
data Test a = Test a
class Repo e ne | ne -> e, e -> ne where
eTable :: Maybe (Test e)
I'm not quite sure what is the deal with Table and Relation types that causes this.
Test is injective, since it is a type constructor.
Relation is not injective, since it is a type family.
Hence the ambiguity.
Silly example:
type instance Relation Bool = ()
type instance Relation String = ()
instance Repo Bool Ne where
eTable :: Table ()
eTable = someEtable1
instance Repo String Ne where
eTable :: Table ()
eTable = someEtable2
Now, what is eTable :: Table () ? It could be the one from the first or the second instance. It is ambiguous since Relation is not injective.
The source of the ambiguity actually has nothing to do with ne not being used in the class (which you headed off by using functional dependencies).
The key part of the error message is:
Expected type: Table (Relation e)
Actual type: Table (Relation e0)
NB: `Database.Selda.Generic.Rel' is a type function, and may not be injective
Note that it's the e that it's having trouble matching up, and the NB message drawing your attention to the issue of type functions and injectivity (you really have to know what that all means for the message to be useful, but it has all the terms you need to look up to understand what's going on, so it's quite good as programming error messages go).
The issue it's complaining about is a key difference between type constructors and type families. Type constructors are always injective, while type functions in general (and type families in particular) do not have to be.
In standard Haskell with no extensions, the only way you can build compound type expressions was using type constructors, such as the left-hand side Test in your data Test a = Test a. I can apply Test (of kind * -> *) to a type like Int (of kind *) to get a type Test Int (of kind *). Type constructors are injective, which means for any two distinct types a and b, Test a is a distinct type from Test b1. This means that when type checking you can "run them backwards"; if I've got two types t1 and t2 that are each the result of applying Test, and I know that t1 and t2 are supposed to be equal, then I can "unapply" Test to get the argument types and check whether those are equal (or infer what one of them is if it was something I hadn't figured out yet and the other is known, or etc).
Type families (or any other form of type function that isn't known to be injective) don't provide us that guarantee. If I have two types t1 and t2 that are supposed to be equal, and they're both the result of applying some TypeFamily, there's no way to go from the resulting types to the types that TypeFamily was applied to. And in particular, there's no way to conclude from the fact that TypeFamily a and TypeFamily b are equal that a and b are equal as well; the type family might just happen to map two distinct types a and b to the same result (the definition of injectivitiy is that it doesn't do that). So if I knew which type a was but didn't know b, knowing that TypeFamily a and TypeFamily b are equal doesn't give me any more information about what type b should be.
Unfortunately, since standard Haskell only has type constructors, Haskell programmers get well-trained to just presume that the type checker can work backwards through compound types to connect up the components. We often don't even notice that the type checker needs to work backwards, we're so used to just looking at type expressions with similar structure and leaping to the obvious conclusions without working through all the steps that the type checker has to go through. But because type checking is based on working out the type of every expression both bottom-up2 and top-down3 and confirming that they are consistent, type checking expressions whose types involve type families can easily run into ambiguity problems where it looks "obviously" unambiguous to us humans.
In your Repo example, consider how the type checker will deal with a position where you use eTable, with (Int, Bool) for e, say. The top-down view will see that it's used in a context where some Table (Relation (Int, Bool)) is required. It'll compute what Relation (Int, Bool) evaluates to: say it's Set Int, so we need Table (Set Int). The bottom-up pass just says eTable can be Table (Relation e) for any e.
All of our experience with standard Haskell tells us that this is obvious, we just instantiate e to (Int, Bool), Relation (Int, Bool) evaluates to Set Int again and we're done. But that's not actually how it works. Because Relation isn't injective there could be some other choice for e for which gives us Set Int for Relation e: perhaps Int. But if we choose e to be (Int, Bool) or Int we need to look for two different Repo instances, which will have different implementations for eTable, even though their type is the same.
Even adding a type annotation every time you use eTable like eTable :: Table (Relation (Int, Bool)) doesn't help. The type annotation only adds extra information to the top-down view, which we often already have anyway. The type-checker is still stuck with the problem that there could be (whether or not there actually are) other choices of e than (Int, Bool) which lead to eTable matching that type annotation, so it doesn't know which instance to use. Any possible use of eTable will have this problem, so it gets reported as an error when you're defining the class. It's basically for the same reason you get problems when you have a class with some members whose types don't use all of the type variables in the class head; you have to consider "variable only used under a type family" as much the same as "variable isn't used at all".
You could address this by adding a Proxy argument to eTable so that there's something fixing the type variable e that the type checker can "run backwards". So eTable :: Proxy e -> Table (Relation e).
Alternatively, with the TypeApplications extension you now can do as the error message suggests and turn on AllowAmbiguousTypes to get the class accepted, and then use things like eTable #(Int, Bool) to tell the compiler which choice for e you want. The reason this works where the type annotation eTable :: Table (Relation (Int, Bool)) doesn't work is the type annotation is extra information added to the context when the type checker is looking top-down, but the type application adds extra information when the type checker is looking bottom-up. Instead of "this expression is required to have a type that unifies with this type" it's "this polymorphic function is instantiated at this type".
1 Type constructors are actually even more restricted than just injectivity; applying Test to any type a results in a type with known structure Test a, so the entire universe of Haskell types is straightforwardly mirrored in types of the form Test t. A more general injective type function could instead do more "rearranging", such as mapping Int to Bool so long as it didn't also map Bool to Bool.
2 From the type produced by combining the sub-parts of the expression
3 From the type required of the context in which it is used
Given the following class definition
class X[+T] {
def get[U>:T](default:U):U
}
why T in the method def get[U>:T](default:U) is in the covariance position
Given the following class definition
class X[-T] {
def get[U<:T](default:U):U
}
why T in the method def get[U<:T](default:U) is in the cotravariance position
It is hard to answer "Why?" question without you providing more details but I'll try. I assume that your question is really "why type restriction on U are not inverted?". The short answer: because this is type-safe and covers some cases that are not covered otherwise.
Your first example is probably inspired by Option[T] and its getOrElse method. Although I'm not sure why anybody needs getOrElse with U different from T, logic why type restriction can be only U>:T seems obvious to me. Let's assume you have 3 classes: C which inherits B which inherits A and you have an Option[B]. If your default value is already B or C you don't need anything beyond U = T and thus simpler signature without additional generic U would suffice. The only case when you can't pass default value to the getOrElse method is if you have it of some type which is not a subtype of B (such as A). Let's extend this signature even more for a moment
def getOrElse[U, R](default:U): R
How types U, T and R should be related? Obviously R should be some common super-type of U and T because it should be able to contain both T and U. However such definition would be an overkill. First of all it is really weird to have default value of a type that is not related to the T at all. Secondly, even if it is such a strange case, you (and compiler) still can calculate a common super-type and assign it to some new U' = R'. Thus you don't need R but adding U adds some more flexibility (at least theoretically). But U still has to be some super-type of T because it is also the return type.
So to sum up: adding U with U<:T will
either produce wrong code if you use U as the result type (U as a result type can't hold T)
or if you use T as the result type would not extend applicability of this method i.e. would not allow any code that does not compile without U to compile with this additional U.
But adding U with U>:T will allow some more code which is actually type-safe to compile such as (yes, stupid example but as I said I don't know any real life examples):
val opt = Option[Int](1)
val orElse: Any = opt.getOrElse(List())
In ghci, I ran:
ghci> :t Right 5
Right 5 :: Num b => Either a b
What's the meaning of a?
How does it compare with Scala's version?
scala> Right(5)
res0: scala.util.Right[Nothing,Int] = Right(5)
a is, like b in this example, a type variable. It can be instantiated with any type (whereas b can be instantiated with any type that satisfies the constraint that it is also an instance of Num).
The scala example works quite differently due to scala's type system being quite different; There is no real concept of a value ever having a not fully instantiated type, so you need to assign a type to the Left possibility of your Either value. Barring further constraints, this just ends up being Nothing. Due to the way scala's type system works (Nothing being a subtype of any other type, so you can think of it as a dual to the Any type) an Either[Nothing,B] is also an Either[A,B] for any A.
In the Twitter Effective Scala - Type Aliases, they say:
Don’t use subclassing when an alias will do.
trait SocketFactory extends (SocketAddress => Socket)
a SocketFactory is a function that produces a Socket. Using a type
alias
type SocketFactory = SocketAddress => Socket
is better. We may now provide function literals for values of type
SocketFactory and also use function composition:
val addrToInet: SocketAddress => Long val inetToSocket: Long => Socket
val factory: SocketFactory = addrToInet andThen inetToSocket
Note that type aliases are not new types — they are equivalent to the syntactically substituting the aliased name for its type.
The sort of thing we're talking about is:
trait Base
trait T1 extends Base // subclassing
type T2 = Base // type alias
Obviously you can't use a type alias as a replacement when the class/trait has a body or stores information.
So using type aliases (T2) rather than extending with a trait or class (T1) has the following advantages:
As they say above, we can compose using function literals.
We won't be producing a .class file, there will be less for the compiler to do (theoretically).
However, it has the following disadvantages:
To be available in the same namespace (package), you need to define the type in a package object, which will probably be in another file from the use site.
You can't jump 'Open Type' ctrl-shift-T on an alias in Eclipse, but you can Open Declaration (F3) in Eclipse. This will probably be fixed in the future.
You can't use the type alias from another language, such as Java.
If the type alias is parameterized, then erasure prevents pattern matching from working in the same manner as it does for traits.
The fourth point is the most serious for me:
trait T1[T]
trait T2 extends T1[Any]
type T3 = T1[Any]
class C2 extends T2
val c = new C2
println("" + (c match { case t: T3 => "T3"; case _ => "any" }))
println("" + (c match { case t: T2 => "T2"; case _ => "any" }))
This produces:
T3
T2
The compiler gives a warning about the first pattern match, which clearly doesn't work as expected.
So, finally, the question. Are there any other advantages or disadvantages to using type aliases rather than extending a trait/class?
I think they key is actually that type aliases and traits are really different. The list of differences goes on and on and on:
Shorthand syntax works for type aliases (e.g. x => x+7 would work as a type I2I = Int => Int) and not traits.
Traits can carry additional data, types aliases can't.
Implicits work for type aliases but not traits.
Traits provide type-safety/matching in a way that type aliases do not.
Type aliases have strict rules about being overridden in subclasses; same-named traits shadow instead (anything goes).
among others.
This is because you are doing dramatically different things in the two cases. Type aliases are simply a way to say, "Okay, when I type Foo, I actually mean Bar. They're the same. Got it? Cool." After you do this, you can substitute the name Foo for Bar wherever and whenever you feel like it. The only restriction is that once you decide what a type is you can't change your mind.
Traits, on the other hand, create an entirely new interface, which may expand on what the trait extends, or may not. If not, it's still a marker that this is its own type of entity, which can be pattern matched, tested for with 'isInstanceOf', and so on.
So, now that we've established that they're really different, the question is how to use each. And the answer is pretty simple: if you like the existing class just as it is except you dislike the name, use a type alias. If you want to create your own new entity that is distinct from other things, use a trait (or a subclass). The former is mostly for convenience, while the latter is for added type safety or capability. I don't think any rule saying use one instead of the other really captures the point--understand the features of both, and use each when those are the features you want.
(And then there's existential types, which provide similar capability to generics...but let's leave that for another question.)
They are different in that a type alias defines type equality relationship (ie. T1 <: T2 && T1 >: T2) while trait extension defines a strict sub-type relationship (ie. T1 <: T2 && !(T1 >: T2)). Use them wisely.