#2904 how to explicitly cast a list to a subtype

pcl Thu 25 Jan

Hello again! (It's been a while since I was last here...)

I've been bitten by a mistake I'm making involving implicit casting. In my program, some operation is resulting in different types for a list, so == does not work.

My question is, how can I explicitly cast a list to its specialised type?

The issue is illustrated by:

static Void main(Str[] args)
{
  val := getList() // Type.of(val) is sys::Obj?[]
  echo(val == ["a"])
}

static Str[] getList()
{
  str := [,]
  str.add("a")
  return str
}

which prints "false".

What can I do to "val" to make it a true Str[]?

An explicit cast does not work:

Str[] val := (Str[])getList()
Type.of(val) // => sys::Obj?[]

The issue is getList not returning a Str[] value, in spite of the type signature. The only (Type-based) way I found to make the program print "true" is changing getList:

str := Str[,]

Edit:

After writing the above, I finally fixed my original program's Type problem, which was caused by using map with an it-block:

fansh> Type.of([1,2,3].map { it*it })
sys::Obj?[]
fansh> Type.of([1,2,3].map |Int x->Int| { x*x })
sys::Int[]

SlimerDude Thu 25 Jan

Hi @pcl,

Everything you mentioned is correct.

To surmise, when creating a List there is the actual list Type and there is the implied runtime list Type.

The actual Type is what the List object is instantiated as, and this cannot be changed.

lst1 := [,]     // <-- creates an Obj[]
lst2 := Str[,]  // <-- creates a  Str[]
lst3 := Int[,]  // <-- creates an Int[]

lst1 has to be of Type Obj[] because we don't know what will be added to it, so, um, what else could it be!?

The compiler is intelligent enough to work out what data the List is instantiated with, and types the List appropriately.

lst4 := ["str", "i", "am"]  // <-- creates a  Str[]
lst5 := [1, 3, 5]           // <-- creates an Int[]
lst6 := ["str", 2]          // <-- creates an Obj[]

This can also be set explicitly.

lst7 := Obj[1, 3, 5]       // <-- creates an Obj[]
lst8 := Str[1, 3, 5]       // <-- Compiler Err

This follows through with closures. List.map() creates and returns a new List, but it doesn't know what data will be in the new List unless we tell the compiler.

lst9 := [1,2,3].map |Int x->Int| { x*x }  //  <-- Int[]

Here we tell the compiler that the closure will always return an Int, so the compiler can safely create and return a List of Int[].

lst10 := [1,2,3].map { x*x }  // <-- Obj[]

But here, the closure could return any object, so the only safe option is for the compiler to create and return a List of Obj[].

Any casting (of Lists) done by any subsequent code help us, the developer, infer what should be in the List, but does not change the backing type of what was actually created.

lst11 := [1, 3, 5]      // <-- a list of Int[]
lst12 := (Obj[]) lst11  // <-- still a list of Int[]

I hope this helps sure up your understanding.

pcl Fri 26 Jan

Thanks @SlimerDude for the explanation. It confirms what I understood, and your statement:

"The actual Type is what the List object is instantiated as, and this cannot be changed."

implies the list equals method should never be used unless you have control over list construction.

Consider this:

class Main
{
  static Void main(Str[] args)
  {
    w := Wrap("HeLlO".chars.map { it.lower } ) // this creates sys::Obj?[]
    w.check
  }
}

class Wrap
{
  Int[] word
  new make (Int[] word) { this.word = word }
  Void check()
  {
    echo(word[0..1] == ['h','e'])
  }
}

If there's no way to cast/force the backing type of "word" in Wrap's constructor to type Int[], then == cannot be used in "check".

What is recommended practice here, if you're writing a library for others to use? Do you write your own "equalContents" method?

There seem to be a few methods which rely on == which would similarly need rewriting, e.g. remove:

fansh> x := [[1], [4], [9]]
[[1], [4], [9]]
fansh> x.remove([4])
[4]
fansh> x
[[1], [9]]
fansh> x = [[1], [4], [9]]
[[1], [4], [9]]
fansh> x.remove([2].map {it*it})
null
fansh> x
[[1], [4], [9]]

SlimerDude Fri 26 Jan

Hi @pcl,

... implies the list equals method should never be used unless you have control over list construction.

... There seem to be a few methods which rely on == which would similarly need rewriting,

It really comes down your requirements, what it is you're trying to do, and what arguments your library API will accept.

The List.equals() method does what it does (checks that two Lists are exactly equal), if you need different behaviour then yes, you'll need to write your contentsAreEqual() method. And if you accept lists of lists, then yes, your contentsAreEqual() will need to be recursive.

If you're developing for SkySpark (as most Fantom developers are!) then you may wish to consider haystack::Etc.listEq.

Login or Signup to reply.