Ensure type parameters are of the same type (not bind to AnyVal)

142 views
Skip to first unread message

Hugo Ferreira

unread,
Nov 17, 2016, 11:14:39 AM11/17/16
to scala-user
Hello,

I have the following function:

 
def combine[ T ]( s: Seq[ Stream[ T ] ] ): Stream[ Seq[ T ] ] = s.map( _.head ) #:: combine( s.map( _.tail ) )

It takes two steam and returns a stream that produces a stream of sequence of values.
Unfortunately if I do:

val s13 = combine( s1, s6 )


I get a type:

val s13: Stream[Seq[AnyVal]]


Because types:

val s1: Stream[Int]
val s6
: Stream[Double]

get the parameters types bound to a common ancestor: AnyVal.
So my question is: how do I enforce the restriction that e types
must be the same. (In the above case it should fail and not bind
to any ancestor or given ancestor).

Would appreciate any pointers.

TIA.
Hugo


Julian Michael

unread,
Nov 18, 2016, 2:27:56 AM11/18/16
to scala-user
Hi Hugo,

I suspect what you want may be impossible. When you say "the types" must be the same, I guess you could either mean

  1. the most specific types, or
  2. the types that would be inferred during typechecking.

If you mean (1), this would be bad, because the most specific type of, for example, Stream.continually(x) may actually be Stream[x.type] or some other refinement that you could imagine (for classes with type members, you could come up with lots of refinements that make their types more specific). This becomes both difficult to decide for the compiler and difficult to understand as a programmer—surely there would be some bad results.

If you mean (2), then I'm not sure how that would work, because the types inferred for the arguments during typechecking depend on the types that are expected in that context. So there's a kind of circularity there.

At the end of the day, your two streams do have the same type: Stream[AnyVal]. They just happen to each have some other types too...

Anyway, my comment on (2) was apparently a joke because we do have a way of doing this: implicit search. Indeed, none of the theoretical mumbo jumbo matters when you have implicit search just absolutely wrecking the place. Something like this might suit you:

scala> implicit class StreamCombine[A](val s: Stream[A]) extends AnyVal {
| def combine(other: Stream[A]): Stream[A] = if(s.isEmpty) other else s.head #:: other.combine(s.tail)
| }
defined class StreamCombine

scala> Stream(1).combine(Stream(1.0))
<console>:14: error: type mismatch;
 found : Double(1.0)
 required: Int
       Stream(1).combine(Stream(1.0))
                                ^

scala> Stream(1).combine(Stream(2))
res3: Stream[Int] = Stream(1, ?)

Hope that helps.

Best,
Julian

Jasper-M

unread,
Nov 18, 2016, 4:45:21 AM11/18/16
to scala-user
Hi,

The combine function you show doesn't really match with the rest of your mail, so that makes it a bit difficult to help (and all the spaces make it a bit difficult to read too...).
But let's assume that your combine function looks like this:

def combine[T](s1: Stream[T], s2: Stream[T]): Stream[Seq[T]] = ???

Now you indeed have the issue that type T is inferred based on both s1 and s2. If they have different types, T will be inferred to be the least upper bound of both.
What you can do is put s1 and s2 in separate parameter lists. Then T will be inferred based only on the first parameter list.

def combine[T](s1: Stream[T])(s2: Stream[T]): Stream[Seq[T]] = ???

Take a look at the following REPL transcript:

scala> def combine1[T](s1: Stream[T], s2: Stream[T]): Stream[Seq[T]] = ???
combine1: [T](s1: Stream[T], s2: Stream[T])Stream[Seq[T]]

scala> def combine2[T](s1: Stream[T])(s2: Stream[T]): Stream[Seq[T]] = ???
combine2: [T](s1: Stream[T])(s2: Stream[T])Stream[Seq[T]]

scala> combine1(Stream.empty[Int], Stream.empty[Double])         // this compiles
scala.NotImplementedError: an implementation is missing
  at scala.Predef$.$qmark$qmark$qmark(Predef.scala:284)
  at .combine1(<console>:11)
  ... 37 elided

scala> combine2(Stream.empty[Int])(Stream.empty[Double])        // this doesn't compile
<console>:13: error: type mismatch;
 found   : scala.collection.immutable.Stream[Double]
 required: Stream[Int]
       combine2(Stream.empty[Int])(Stream.empty[Double])
                                               ^

kr,
Jasper

Op donderdag 17 november 2016 17:14:39 UTC+1 schreef Hugo Ferreira:

Hugo Ferreira

unread,
Nov 18, 2016, 5:46:13 AM11/18/16
to scala-user
Hi Julian,


On Friday, 18 November 2016 07:27:56 UTC, Julian Michael wrote:
Hi Hugo,

I suspect what you want may be impossible. When you say "the types" must be the same, I guess you could either mean

  1. the most specific types, or
  2. the types that would be inferred during typechecking.

If you mean (1), this would be bad, because the most specific type of, for example, Stream.continually(x) may actually be Stream[x.type] or some other refinement that you could imagine (for classes with type members, you could come up with lots of refinements that make their types more specific). This becomes both difficult to decide for the compiler and difficult to understand as a programmer—surely there would be some bad results.

If you mean (2), then I'm not sure how that would work, because the types inferred for the arguments during typechecking depend on the types that are expected in that context. So there's a kind of circularity there.

At the end of the day, your two streams do have the same type: Stream[AnyVal]. They just happen to each have some other types too...


I am trying to see if it is really 1 or 2, and I think it is 1.
 
Anyway, my comment on (2) was apparently a joke because we do have a way of doing this: implicit search. Indeed, none of the theoretical mumbo jumbo matters when you have implicit search just absolutely wrecking the place. Something like this might suit you:

scala> implicit class StreamCombine[A](val s: Stream[A]) extends AnyVal {
| def combine(other: Stream[A]): Stream[A] = if(s.isEmpty) other else s.head #:: other.combine(s.tail)
| }
defined class StreamCombine

scala> Stream(1).combine(Stream(1.0))
<console>:14: error: type mismatch;
 found : Double(1.0)
 required: Int
       Stream(1).combine(Stream(1.0))
                                ^

scala> Stream(1).combine(Stream(2))
res3: Stream[Int] = Stream(1, ?)

Hope that helps.


Interesting. During my experiments I wrote:

def combine[ T, T1 <: T, T2 <: T ]( s1: Stream[ T1 ], s2: Stream[ T2 ] )( implicit ev: T1 =:= T2 ): Stream[ Seq[ T ] ] = combine( Array( s1, s2 ) )

To test enforcing the same type via implicits for two arguments only. When I try:

 val s1: Stream[Int]
 val s6
: Stream[Double]


 val s13
= combine( s1, s6 )

I get:

Multiple markers at this line:
   
- not enough arguments for method combine: (implicit ev:
     
=:=[Int,Double])Stream[Seq[AnyVal]]. Unspecified value parameter ev.
   
- Cannot prove that Int =:= Double.

which is what I want. So I realize that what I want (need?) is to ensure that a Seq[T] (in the initial example I use a list to hold the streams)
have the same most specific type - which is case (1). So if I understand you correctly, this is impossible to enforce.

As for your example, I confess I don't quite get it. If I had a sequence of streams, how could I use that technique?
Also, I don't see how that works if you have streams with various elements. The code seems to return either one or the other.
I will have too look at this more carefully. 

Thanks for the help,
Hugo F

Hugo Ferreira

unread,
Nov 18, 2016, 6:10:22 AM11/18/16
to scala-user
Hi Jasper,



On Friday, 18 November 2016 09:45:21 UTC, Jasper-M wrote:
Hi,

The combine function you show doesn't really match with the rest of your mail, so that makes it a bit difficult to help (and all the spaces make it a bit difficult to read too...).
But let's assume that your combine function looks like this:

def combine[T](s1: Stream[T], s2: Stream[T]): Stream[Seq[T]] = ???

Not quite. I really have:

def combine[T](s:Seq[Stream[T]]): Stream[Seq[T]] = ???


Now you indeed have the issue that type T is inferred based on both s1 and s2. If they have different types, T will be inferred to be the least upper bound of both.
What you can do is put s1 and s2 in separate parameter lists. Then T will be inferred based only on the first parameter list.

def combine[T](s1: Stream[T])(s2: Stream[T]): Stream[Seq[T]] = ???

Take a look at the following REPL transcript:

scala> def combine1[T](s1: Stream[T], s2: Stream[T]): Stream[Seq[T]] = ???
combine1: [T](s1: Stream[T], s2: Stream[T])Stream[Seq[T]]

scala> def combine2[T](s1: Stream[T])(s2: Stream[T]): Stream[Seq[T]] = ???
combine2: [T](s1: Stream[T])(s2: Stream[T])Stream[Seq[T]]

scala> combine1(Stream.empty[Int], Stream.empty[Double])         // this compiles
scala.NotImplementedError: an implementation is missing
  at scala.Predef$.$qmark$qmark$qmark(Predef.scala:284)
  at .combine1(<console>:11)
  ... 37 elided

scala> combine2(Stream.empty[Int])(Stream.empty[Double])        // this doesn't compile
<console>:13: error: type mismatch;
 found   : scala.collection.immutable.Stream[Double]
 required: Stream[Int]
       combine2(Stream.empty[Int])(Stream.empty[Double])
                                               ^


Ok. Learned something new. This may not be helpful to solve what I have, which really seems to be: ensuring that a Seq[T]
uses only a single type T and not its LUB. So a List(1, 2, 3, 1.0) should actually fail. From my understanding of Julian's
response, this is not possible.  

Appreciate the information.

R,
Hugo
 

Marconi

unread,
Nov 18, 2016, 11:03:36 AM11/18/16
to scala-user
you could use implicit evidence:

scala> class A
defined class A

scala> class B extends A
defined class B

scala> class C
defined class C

scala> val a = new A
a: A = A@3889c343

scala> val b = new B
b: B = B@7a6e0f3c

scala> val c = new C
c: C = C@6ac0fd1d

scala> def c1[T](a: T, b: T): (T, T) = (a, b)
c1: [T](a: T, b: T)(T, T)

scala> c1(a, b)
res3: (A, A) = (A@3889c343,B@7a6e0f3c)

scala> c1(a, c)
res4: (Object, Object) = (A@3889c343,C@6ac0fd1d)

scala> def c2[T, U](a: T, b: U)(implicit ev: U <:< T): (T, U) = (a, b)
c2: [T, U](a: T, b: U)(implicit ev: <:<[U,T])(T, U)

scala> c2(a, b)
res6: (A, B) = (A@3889c343,B@7a6e0f3c)

scala> c2(a, c)
<console>:48: error: Cannot prove that C <:< A.
c2(a, c)

Oliver Ruebenacker

unread,
Nov 18, 2016, 11:55:19 AM11/18/16
to Hugo Ferreira, scala-user

     Hello,

  To make sure the type is some primitive number type, but not AnyVal, you can apply some operator that works on all numbers, but not AnyVal, such as < or >:

scala> Seq(1, 2, 3).forall(x => x > x)
res5: Boolean = false

scala> (Seq(1, 2, 3): Seq[AnyVal]).forall(x => x > x)
<console>:12: error: value > is not a member of AnyVal
       (Seq(1, 2, 3): Seq[AnyVal]).forall(x => x > x)
                                                 ^


     Best, Oliver


--
You received this message because you are subscribed to the Google Groups "scala-user" group.
To unsubscribe from this group and stop receiving emails from it, send an email to scala-user+unsubscribe@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.



--
Oliver Ruebenacker
Senior Software Engineer, Diabetes Portal, Broad Institute

Julian Michael

unread,
Nov 18, 2016, 2:42:58 PM11/18/16
to scala-user
Hugo,

 
So I realize that what I want (need?) is to ensure that a Seq[T] (in the initial example I use a list to hold the streams)
have the same most specific type - which is case (1). So if I understand you correctly, this is impossible to enforce.
 ...
> This may not be helpful to solve what I have, which really seems to be: ensuring that a Seq[T]
uses only a single type T and not its LUB.

Yes, essentially my point (1) was that there is actually no difference between using a single type T and the LUB of the elements of the stream, or if there was, it wouldn't be what you want: for example, Seq(1.0, 2.0) could be rejected because the singleton types Double(1.0) and Double(2.0) don't agree.

 
As for your example, I confess I don't quite get it. If I had a sequence of streams, how could I use that technique?
Also, I don't see how that works if you have streams with various elements. The code seems to return either one or the other.
I will have too look at this more carefully. 

I'm not exactly sure what you mean by "various elements." If you look closely at the function I wrote, it implements a special case of your combine from the OP where you only want to combine two streams—and it just interleaves their elements. (Actually, your function will throw an error when it encounters the end of a stream, so they're not quite the same.) But yes, it's not clear to me how to generalize this to an arbitrary stream. It's definitely the case that if you get, for example, a Stream[AnyVal] from elsewhere, there is no way of enforcing with the type system that all of its elements are, say, of type Int, because that information is lost. In fact, it's not even possible to write a function to do that at runtime and always terminate.

More broadly speaking, I suggest you reconsider whether what you're trying to accomplish is necessary at all. When the typechecker takes a really high LUB like AnyVal or Any, usually it causes type errors immediately when you try to use the stream again—so you'll pretty much always catch the unwanted behavior at compile time.

On the other hand, you might end up in situations where you do want a Seq of Streams of LUBs of the types in question. Suppose you want to construct a Seq of Streams of Options and do something like Seq(Stream(None), Stream(Some(5), ...), ...). It's quite desirable for the typechecker to take the LUB and not yell at you about Some[Int] not being a subtype of None.type. (Indeed, I often end up up-casting None or replacing it with another expression to avoid this kind of thing, because the typechecker isn't aggressive enough to catch all of these cases.)

Hugo Ferreira

unread,
Nov 21, 2016, 5:52:08 AM11/21/16
to scala-user
Hello Marconi,

Below you show the use of "<:<" to ensure the correct type.
Note that I have already done this with "=:=". I finally came to
realize that my issue is that Scala containers with several types
that may bind to "Any". My question is: how do I avoid this without
imposing the need for uses to set the type explicitly.

In other words how would you proceed as you did below when
we pass a single Seq[T] and not two parameters a and b of type T.

Thanks for the input.

Hugo Ferreira

unread,
Nov 21, 2016, 5:54:33 AM11/21/16
to scala-user, hugo6f...@gmail.com
Hello Oliver,

This hack may do it. Have to see how I can deal with characters and strings.
I will be using streams but I don't think that is an issue.

Thanks.
To unsubscribe from this group and stop receiving emails from it, send an email to scala-user+...@googlegroups.com.

For more options, visit https://groups.google.com/d/optout.

Hugo Ferreira

unread,
Nov 21, 2016, 6:11:15 AM11/21/16
to scala-user
Hello Julian,


On Friday, 18 November 2016 19:42:58 UTC, Julian Michael wrote:
Hugo,
 
So I realize that what I want (need?) is to ensure that a Seq[T] (in the initial example I use a list to hold the streams)
have the same most specific type - which is case (1). So if I understand you correctly, this is impossible to enforce.
 ...
> This may not be helpful to solve what I have, which really seems to be: ensuring that a Seq[T]
uses only a single type T and not its LUB.

Yes, essentially my point (1) was that there is actually no difference between using a single type T and the LUB of the elements of the stream, or if there was, it wouldn't be what you want: for example, Seq(1.0, 2.0) could be rejected because the singleton types Double(1.0) and Double(2.0) don't agree.
 

I just wanted to ensure that no Any would be bound to. If I have a Seq[T] and they numerical (Inte and Double), then it is ok to bind to a Double.
The operators in one are the same ion another. However the problem is we use characters and strings that are treated as numeric (Scala 2.12.0)

 
As for your example, I confess I don't quite get it. If I had a sequence of streams, how could I use that technique?
Also, I don't see how that works if you have streams with various elements. The code seems to return either one or the other.
I will have too look at this more carefully. 

I'm not exactly sure what you mean by "various elements."

Apologies. "Various elements" here means several several streams each of a different type.

 
If you look closely at the function I wrote, it implements a special case of your combine from the OP where you only want to combine two streams—and it just interleaves their elements. (Actually, your function will throw an error when it encounters the end of a stream, so they're not quite the same.)

I doesn't throw an exception. I have tested it. Note also that we return a stream of a sequence. We don't combine elements in a serial fashion.
 
But yes, it's not clear to me how to generalize this to an arbitrary stream. It's definitely the case that if you get, for example, a Stream[AnyVal] from elsewhere, there is no way of enforcing with the type system that all of its elements are, say, of type Int, because that information is lost. In fact, it's not even possible to write a function to do that at runtime and always terminate.


Type erasure. I will see if the use of Type Tags can be used. But this will result in run-time errors - which is what I wanted to avoid.
Or maybe a HList is what I need.
 
More broadly speaking, I suggest you reconsider whether what you're trying to accomplish is necessary at all. When the typechecker takes a really high LUB like AnyVal or Any, usually it causes type errors immediately when you try to use the stream again—so you'll pretty much always catch the unwanted behavior at compile time.


Is suspect not  immediately. Only when I operate on Any will an error be generated.
 
On the other hand, you might end up in situations where you do want a Seq of Streams of LUBs of the types in question. Suppose you want to construct a Seq of Streams of Options and do something like Seq(Stream(None), Stream(Some(5), ...), ...). It's quite desirable for the typechecker to take the LUB and not yell at you about Some[Int] not being a subtype of None.type. (Indeed, I often end up up-casting None or replacing it with another expression to avoid this kind of thing, because the typechecker isn't aggressive enough to catch all of these cases.)
 

In this case I think stricter type checking is necessary. Seeing as this is not easy, I will try and move on and see if LUBs can  be used successfully.

Thanks for the feedback. Appreciate it.

Regards,
Hugo


 

Jasper-M

unread,
Nov 21, 2016, 8:48:19 AM11/21/16
to scala-user
If excluding Any, AnyVal and AnyRef is enough for you, you can use something like this toy project of me.

import implicitlogic._
def combine[T](s: Seq[Stream[T]])(implicit ev: Not[(AnyVal <:< T) Or (AnyRef <:< T)]): Stream[Seq[T]] = ???



Op maandag 21 november 2016 11:52:08 UTC+1 schreef Hugo Ferreira:

Hugo Ferreira

unread,
Nov 22, 2016, 9:13:57 AM11/22/16
to scala-user
Hello Jasper-M,



On Monday, 21 November 2016 13:48:19 UTC, Jasper-M wrote:
If excluding Any, AnyVal and AnyRef is enough for you, you can use something like this toy project of me.

import implicitlogic._
def combine[T](s: Seq[Stream[T]])(implicit ev: Not[(AnyVal <:< T) Or (AnyRef <:< T)]): Stream[Seq[T]] = ???



This works as expected. If I pass a List whose elements' LUB is  Any, the compiler generates an error.
My Scala-fu is not enough to understand this. I will have to learn more to get this.

Thank you very much.

Hugo F.

Reply all
Reply to author
Forward
0 new messages