scala / scala3

The Scala 3 compiler, also known as Dotty.
https://dotty.epfl.ch
Apache License 2.0
5.89k stars 1.06k forks source link

unexpected result from functions in file but not from code in console #18107

Open oivulf opened 1 year ago

oivulf commented 1 year ago

Compiler version

tried 3.1.1 and 3.2.1

Minimized code

I attach the build.sbt and single testA.sc source file. test in sbt console with :load testA.sc val (res, p)=doTest val (res, p)=doTest res.map(p)

Expectation

my expectation would be that res.map(p) yield always the same value, that is the one printed the first time one runs doTest and when issuing res.map(p) in console. however, res.map(p) in the code and in console are behaving differently since the second time doTest is called. I understand that results may differ through different calls of doTest, since an external library is involved, however the fact remains that res.map(p) behaves differently inside doTest and outside.

bug.zip

som-snytt commented 1 year ago

The example is far from minimized.

I think the expectation is that these printed results should be the same.

scala> doTest
List(0, 0, 0, 12, 0)
Some([I@1f018573)
List(0, 0, 0, 12, 0)
val res0: (org.openscience.cdk.interfaces.IAtomContainer => Array[Int],
  List[org.openscience.cdk.interfaces.IAtomContainer]
) = (repl$.rs$line$1$$$Lambda$6237/0x0000000802008000@6d1955cd,List(AtomContainer(2070970081, #A:12, AtomRef{Atom(1376222431, S:C, H:1, AtomType(1376222431, N:C.sp2, MBO:DOUBLE, BOS:4.0, FC:0, H:SP2, NC:3, EV:4, Isotope(1376222431, Element(1376222431, S:C, ID:0, AN:6))))}, AtomRef{Atom(1444430416, S:C, H:1, AtomType(1444430416, N:C.sp2, MBO:DOUBLE, BOS:4.0, FC:0, H:SP2, NC:3, EV:4, Isotope(1444430416, Element(1444430416, S:C, ID:1, AN:6))))}, AtomRef{Atom(2011567375, S:C, H:1, AtomType(2011567375, N:C.sp2, MBO:DOUBLE, BOS:4.0, FC:0, H:SP2, NC:3, EV:4, Isotope(2011567375, Element(2011567375, S:C, ID:2, AN:6))))}, AtomRef{Atom(1001897799, S:C, H:0, AtomType(1001897799, N:C.sp2, MBO:DOUBLE, BOS:4.0, FC:0, H:SP2, NC:3, EV:4, Isotope(1001897799, Element(1001897799, S:C, ID:3, AN:6))))}, AtomRef{Atom(1379913066, S:C, H:0, AtomType(1379913066, N:C.sp2, MBO:DOUBLE, BOS:4.0, FC:0, H:SP2, NC:3, EV:4, Isotope(1379913066, Element(1379913066, S:C, ID:4, AN:6))))}, AtomRef{Atom(1176575935, S:C, H:1, At ... large output truncated, print value to show all

scala> doTest
List(0, 0, 0, 0, 0)
None
List(0, 0, 0, 0, 0)
val res1: (org.openscience.cdk.interfaces.IAtomContainer => Array[Int],
  List[org.openscience.cdk.interfaces.IAtomContainer]
) =

Here is an example where the first run looks wrong:

sbt:fresco> console
Welcome to Scala 3.1.1 (20.0.1, Java OpenJDK 64-Bit Server VM).
Type in expressions for evaluation. Or try :help.

scala> :load testA.sc
val T4aSMI: String = C1=CC2=C3C=CC=CC3=C2C=C1
val T4bSMI: String = c1ccc2c(c1)-c1ccccc1-2
val SRG: org.openscience.cdk.tools.StructureResonanceGenerator = org.openscience.cdk.tools.StructureResonanceGenerator@6a92a020
val smilesParser: org.openscience.cdk.smiles.SmilesParser = org.openscience.cdk.smiles.SmilesParser@c04827e
val aromaPerc: org.openscience.cdk.aromaticity.Aromaticity = org.openscience.cdk.aromaticity.Aromaticity@5a5c9ed4
def molDaSmiles(smi: String): org.openscience.cdk.interfaces.IAtomContainer
def mappaAtomi
  (m1: org.openscience.cdk.interfaces.IAtomContainer, m2:
    org.openscience.cdk.interfaces.IAtomContainer
  ): (org.openscience.cdk.interfaces.IAtomContainer => Array[Int],
    List[org.openscience.cdk.interfaces.IAtomContainer]
  , Option[Array[Int]])
def doTest: (org.openscience.cdk.interfaces.IAtomContainer => Array[Int],
  List[org.openscience.cdk.interfaces.IAtomContainer]
)

scala> doTest
List(0, 0, 0, 0, 0)
None
List(0, 0, 0, 0, 0)
val res0: (org.openscience.cdk.interfaces.IAtomContainer => Array[Int],
  List[org.openscience.cdk.interfaces.IAtomContainer]
) = 

Presumably, something else is broken about the example code.

oivulf commented 1 year ago

I did not understand, did you find anything wrong in my scala code? doTest returns also res and p because the result puzzled me much. if you try res.map(p) in the console it always yields the same result, that is at least one non-empty array, given the input.

scala> res.map(p) val res4: List[Array[Int]] = List(Array(), Array(), Array(), Array(0, 5, 4, 6, 7, 8, 9, 10, 11, 3, 2, 1 ), Array())

som-snytt commented 1 year ago

Following your steps,

scala> res.map(p)
-- [E008] Not Found Error: -----------------------------------------------------
1 |res.map(p)
  |^^^^^^^
  |value map is not a member of org.openscience.cdk.interfaces.IAtomContainer => Array[Int]
1 error found

I ran out of time this morning, but I wanted some practice using scala-cli, which shines at a reproduction like this one. It's much more convenient than a zip file with build.sbt.

➜  i18107 scala-cli repl --dependency org.openscience.cdk:cdk-bundle:2.8
Welcome to Scala 3.2.2 (20.0.1, Java OpenJDK 64-Bit Server VM).
Type in expressions for evaluation. Or try :help.

scala> :load testA.sc
there was 1 deprecation warning; re-run with -deprecation for details
1 warning found
val T4aSMI: String = C1=CC2=C3C=CC=CC3=C2C=C1
val T4bSMI: String = c1ccc2c(c1)-c1ccccc1-2
val SRG: org.openscience. [snip]

or at the top of your source

//> using dep org.openscience.cdk:cdk-bundle:2.8

and then

scala-cli repl test.scala

I haven't examined your source, because I'm not sure what the reproduction is, see my previous console session where the first run already doesn't look right.

The OP test file is

//> using dep org.openscience.cdk:cdk-bundle:2.8

import org.openscience.cdk as cdk
import cdk.interfaces._
import cdk.isomorphism.Pattern
import collection.convert.ImplicitConversions._
import cdk.smiles.SmilesParser
import cdk.silent.SilentChemObjectBuilder
import cdk.tools.StructureResonanceGenerator
import cdk.aromaticity.{Aromaticity, ElectronDonation}
import cdk.graph.Cycles

val T4aSMI="C1=CC2=C3C=CC=CC3=C2C=C1"
val T4bSMI="c1ccc2c(c1)-c1ccccc1-2"

val SRG=new StructureResonanceGenerator()
val smilesParser=SmilesParser(SilentChemObjectBuilder.getInstance())
val aromaPerc=new Aromaticity(ElectronDonation.piBonds(), Cycles.cdkAromaticSet())

def molDaSmiles(smi:String)= {
  val mol=smilesParser.parseSmiles(smi)
  aromaPerc.apply(mol)
  mol
}

def mappaAtomi(m1: IAtomContainer, m2: IAtomContainer)= {
  val p=Pattern.findIdentical(m1).`match`
  p(m2)
  val res=SRG.getStructures(m2).atomContainers.toList
  // Pattern.findIdentical(m1).`match`(m3)
  // val res=m2::(SRG.getStructures(m2).atomContainers.toList:+m2)
  println(res.map(p).map(_.length))
  (p, res, res.map(p).find(! _.isEmpty) )
}

def doTest={
  val mx=molDaSmiles(T4aSMI)
  val my=molDaSmiles(T4bSMI)
  val (p, res, m)=mappaAtomi(mx, my)
  println(m)
  println(res.map(p).map(_.length))
  (p, res)
}
oivulf commented 1 year ago

As far as concerns your try above:

scala> res.map(p) -- [E008] Not Found Error: ----------------------------------------------------- 1 |res.map(p) |^^^^^^^ |value map is not a member of org.openscience.cdk.interfaces.IAtomContainer => Array[Int] 1 error found

I think you switched res and p. res is of type List[IAtomContainer] and p is of kind IAtomContainer => Array[Int], so res.map(p) makes sense.

the test I suggested is val (p, res)=doTest res.map(p)

oivulf commented 1 year ago

I shortened the source a bit, please find it below. You can run it in scala-cli as suggested by som-snytt, with the command

scala-cli repl --dependency org.openscience.cdk:cdk-bundle:2.8

Once again, r2.map(p2) in the source file yields the wrong result, both in the function and outside, except the first time it is called. however, repeated in the console it yields always the right one.

//----------------------------------- import org.openscience.cdk as cdk import cdk.interfaces. import cdk.isomorphism.Pattern import collection.convert.ImplicitConversions. import cdk.smiles.SmilesParser import cdk.silent.SilentChemObjectBuilder import cdk.tools.StructureResonanceGenerator import cdk.aromaticity.{Aromaticity, ElectronDonation} import cdk.graph.Cycles

val T4aSMI="C1=CC2=C3C=CC=CC3=C2C=C1" val T4bSMI="c1ccc2c(c1)-c1ccccc1-2"

val SRG=new StructureResonanceGenerator() val smilesParser=SmilesParser(SilentChemObjectBuilder.getInstance()) val aromaPerc=new Aromaticity(ElectronDonation.piBonds(), Cycles.cdkAromaticSet())

def molDaSmiles(smi:String)= { val mol=smilesParser.parseSmiles(smi) aromaPerc.apply(mol) mol }

def mappaAtomi(m1: IAtomContainer, m2: IAtomContainer)= { val p=Pattern.findIdentical(m1).match p(m2) val res=SRG.getStructures(m2).atomContainers println(res.map(p).map(.length)) (p, res, res.map(p).find(! .isEmpty) ) }

val (p1, r1, m1)=mappaAtomi(molDaSmiles(T4aSMI), molDaSmiles(T4bSMI)) val (p2, r2, m2)=mappaAtomi(molDaSmiles(T4aSMI), molDaSmiles(T4bSMI)) r2.map(p2) //------------------------------------------------------------------------------------

oivulf commented 1 year ago

I modified the code to make it compatible with scala2, and executed it both with scala 3.2.1 and with scala 2.13, e.g.:

scala-cli repl --scala 2.13 --dependency org.openscience.cdk:cdk-bundle:2.8 and scala-cli repl --dependency org.openscience.cdk:cdk-bundle:2.8 and they behave differently. below the source

//------------------------------------------------------------------ import org.openscience.{cdk => cdk} import cdk.interfaces. import cdk.isomorphism.Pattern import collection.convert.ImplicitConversions. import cdk.smiles.SmilesParser import cdk.silent.SilentChemObjectBuilder import cdk.tools.StructureResonanceGenerator import cdk.aromaticity.{Aromaticity, ElectronDonation} import cdk.graph.Cycles

val T4aSMI="C1=CC2=C3C=CC=CC3=C2C=C1" val T4bSMI="c1ccc2c(c1)-c1ccccc1-2"

val SRG=new StructureResonanceGenerator() val smilesParser=new SmilesParser(SilentChemObjectBuilder.getInstance()) val aromaPerc=new Aromaticity(ElectronDonation.piBonds(), Cycles.cdkAromaticSet())

def molDaSmiles(smi:String)= { val mol=smilesParser.parseSmiles(smi) aromaPerc.apply(mol) mol }

def mappaAtomi(m1: IAtomContainer, m2: IAtomContainer)= { val p=Pattern.findIdentical(m1).match() p(m2) val res=SRG.getStructures(m2).atomContainers println(res.map(p).map(.length)) (p, res, res.map(p).find(! _.isEmpty) ) }

val (p1, r1, m1)=mappaAtomi(molDaSmiles(T4aSMI), molDaSmiles(T4bSMI)) val (p2, r2, m2)=mappaAtomi(molDaSmiles(T4aSMI), molDaSmiles(T4bSMI)) r2.map(p2) //------------------------------------------------------------------------------------

oivulf commented 1 year ago

I also tested the code in a debugger. As soon as I set a breakpoint and step into mappaAtomi, however, the bug does not manifest and mappaAtomi behaves correctly. Conversely, if I do not step into mappaAtomi, it does not work.

som-snytt commented 1 year ago

That sounds like a race condition. (But I haven't looked at this issue again.) Scala 2 REPL once suffered from bugs in the resident compiler, so anything is possible. "It only works the first time" sounds familiar.