Násobení matic a automatické přetypování

Inkvizitor

Re:Násobení matic a automatické přetypování
« Odpověď #45 kdy: 17. 09. 2015, 23:04:36 »
OK, řešení č. 2, snad už projde přes komisi:

Kód: [Vybrat]
package casematrix {

abstract class Matrix(array: Array[Array[Double]]) {
  private val elements = array
  def toArray(): Array[Array[Double]] = {
    this.elements
  }
}
object Matrix {
  def newMatrix(array: Array[Array[Double]]) : Matrix = {
    if (array.length == array(0).length) new SquareMatrix(array)
    else new RectangleMatrix(array)
  }
  def multiply(first: Matrix, second: Matrix) = {
    newMatrix(first.toArray()) // dummy
  }
}

case class RectangleMatrix(array : Array[Array[Double]]) extends Matrix(array)

case class SquareMatrix(array : Array[Array[Double]]) extends Matrix(array) {
  def trace() : Float = {
    return 0; // dummy
  }
}

}

import java.lang.RuntimeException
import casematrix._

object MatrixCaseApp extends App {
  val m = Matrix.newMatrix(Array(Array(1.0, 2.0),Array(1.0, 2.0)))
  val n = Matrix.newMatrix(Array(Array(1.0, 2.0),Array(1.0, 2.0)))
  Matrix.multiply(m, n) match {
    case m @ SquareMatrix(_) => println(m.trace())
    case _ => println("Sorry, not a square matrix")
  }
}


zboj

  • *****
  • 1 507
    • Zobrazit profil
    • E-mail
Re:Násobení matic a automatické přetypování
« Odpověď #46 kdy: 17. 09. 2015, 23:07:14 »
Diskuse se zase zvrtla, tak to shrňme. Jsou tři možnosti:

1. Konverze Matrix na SquareMatrix, vznikne nový objekt, který ale může využít "vnitřnosti" obecnější matice. V C++ by pak bylo něco jako SquareMatrix(a*b).trace() (násobení vždy vrací instanci Matrix).

2. Dynamické řešení, násobení vrací instanci Matrix nebo SquareMatrix podle rozměrů. Použití: ((a*b) as SquareMatrix).trace() (nic nového se nevytváří).

3. Mít dvě implementace metody pro násobení a využít inferenci typu a polymorfismus návratového typu; není nutné nic kopírovat ani použít as, prostě se napíše (a*b).trace() ("magii" udělá překladač v době kompilace). Nevýhody: jde to jen ve Swiftu (ze zmiňovaných jazyků) a ti jednodušší (programátoři-praktici, jak je někdo nazval :) ) to nejsou schopni pochopit. Je fakt, že příklad by šel asi vymyslet lepší.

(4. Mít jen jednu třídu, to ale není odpověď na otázku.)

Inkvizitor

Re:Násobení matic a automatické přetypování
« Odpověď #47 kdy: 17. 09. 2015, 23:14:13 »
Řeší problém

... stejně idiotsky jako pokusit se přetypovat výsledek a čekat zda se nestane ClassCastException.

Mohl bych se urazit, ale proč. Furt je to lepší než to, co jsi nabídl Ty (zkontrolovat třídu a pak přetypovat), protože to co nabízíš Ty, to umí samozřejmě automaticky taky. Implicitní konverze emuluje chování programu v dynamickém jazyce. Ovšem moje druhé řešení je typově bezpečné a bez trapných obezliček ve stylu instanceof.

zboj

  • *****
  • 1 507
    • Zobrazit profil
    • E-mail
Re:Násobení matic a automatické přetypování
« Odpověď #48 kdy: 17. 09. 2015, 23:14:24 »
OK, řešení č. 2, snad už projde přes komisi:

Kód: [Vybrat]
package casematrix {

abstract class Matrix(array: Array[Array[Double]]) {
  private val elements = array
  def toArray(): Array[Array[Double]] = {
    this.elements
  }
}
object Matrix {
  def newMatrix(array: Array[Array[Double]]) : Matrix = {
    if (array.length == array(0).length) new SquareMatrix(array)
    else new RectangleMatrix(array)
  }
  def multiply(first: Matrix, second: Matrix) = {
    newMatrix(first.toArray()) // dummy
  }
}

case class RectangleMatrix(array : Array[Array[Double]]) extends Matrix(array)

case class SquareMatrix(array : Array[Array[Double]]) extends Matrix(array) {
  def trace() : Float = {
    return 0; // dummy
  }
}

}

import java.lang.RuntimeException
import casematrix._

object MatrixCaseApp extends App {
  val m = Matrix.newMatrix(Array(Array(1.0, 2.0),Array(1.0, 2.0)))
  val n = Matrix.newMatrix(Array(Array(1.0, 2.0),Array(1.0, 2.0)))
  Matrix.multiply(m, n) match {
    case m @ SquareMatrix(_) => println(m.trace())
    case _ => println("Sorry, not a square matrix")
  }
}

Scalu detailně neznám, ale předpokládám, že lépe to v ní nepůjde.

ded.kenedy

Re:Násobení matic a automatické přetypování
« Odpověď #49 kdy: 18. 09. 2015, 00:34:20 »
Citace
Mít dvě implementace metody pro násobení a využít inferenci typu a polymorfismus návratového typu; není nutné nic kopírovat ani použít as, prostě se napíše (a*b).trace()

A jak dopadne vysledek prekladu/vyhodnoceni, pokud:
Kód: [Vybrat]
a = matrix(2,3); b = matrix(3,4)
Pro uplnost dodam, ze predpokladam, ze hodnoty promennych jsou znamy az za behu programu.


zboj

  • *****
  • 1 507
    • Zobrazit profil
    • E-mail
Re:Násobení matic a automatické přetypování
« Odpověď #50 kdy: 18. 09. 2015, 00:40:36 »
Citace
Mít dvě implementace metody pro násobení a využít inferenci typu a polymorfismus návratového typu; není nutné nic kopírovat ani použít as, prostě se napíše (a*b).trace()

A jak dopadne vysledek prekladu/vyhodnoceni, pokud:
Kód: [Vybrat]
a = matrix(2,3); b = matrix(3,4)
Pro uplnost dodam, ze predpokladam, ze hodnoty promennych jsou znamy az za behu programu.

Běh blbě, protože použitím trace říkám, že matice je čtvercová, takže někde bouchne assert.

Inkvizitor

Re:Násobení matic a automatické přetypování
« Odpověď #51 kdy: 18. 09. 2015, 07:36:43 »
Scalu detailně neznám, ale předpokládám, že lépe to v ní nepůjde.

Já v ní také nedělám, takže mi včera večer například nedocvaklo, že je zbytečné používat case class, když se dá matchnout normální třída třeba takto:

Kód: [Vybrat]
object MatrixCaseApp extends App {
  val m = Matrix.newMatrix(Array(Array(1.0, 2.0),Array(1.0, 2.0)))
  val n = Matrix.newMatrix(Array(Array(1.0, 2.0),Array(1.0, 2.0)))
  Matrix.multiply(m, n) match {
    case x: SquareMatrix => println(x.trace())
    case _ => println("Sorry, not a square matrix")
  }
}

To jsou ale detaily. Faktem je, že jsem si pomohl pattern matchingem, který Scala zdědila z FP větve a ne z OOP.

zboj

  • *****
  • 1 507
    • Zobrazit profil
    • E-mail
Re:Násobení matic a automatické přetypování
« Odpověď #52 kdy: 18. 09. 2015, 07:37:50 »
Řeší problém

... stejně idiotsky jako pokusit se přetypovat výsledek a čekat zda se nestane ClassCastException.

Mohl bych se urazit, ale proč. Furt je to lepší než to, co jsi nabídl Ty (zkontrolovat třídu a pak přetypovat), protože to co nabízíš Ty, to umí samozřejmě automaticky taky. Implicitní konverze emuluje chování programu v dynamickém jazyce. Ovšem moje druhé řešení je typově bezpečné a bez trapných obezliček ve stylu instanceof.
Proč tam je RectangleMatrix?

zboj

  • *****
  • 1 507
    • Zobrazit profil
    • E-mail
Re:Násobení matic a automatické přetypování
« Odpověď #53 kdy: 18. 09. 2015, 07:41:23 »
Scalu detailně neznám, ale předpokládám, že lépe to v ní nepůjde.

Já v ní také nedělám, takže mi včera večer například nedocvaklo, že je zbytečné používat case class, když se dá matchnout normální třída třeba takto:

Kód: [Vybrat]
object MatrixCaseApp extends App {
  val m = Matrix.newMatrix(Array(Array(1.0, 2.0),Array(1.0, 2.0)))
  val n = Matrix.newMatrix(Array(Array(1.0, 2.0),Array(1.0, 2.0)))
  Matrix.multiply(m, n) match {
    case x: SquareMatrix => println(x.trace())
    case _ => println("Sorry, not a square matrix")
  }
}

To jsou ale detaily. Faktem je, že jsem si pomohl pattern matchingem, který Scala zdědila z FP větve a ne z OOP.

Pattern matching je jedno z možných řešení, ale také to je jen zakuklené přetypování.

zboj

  • *****
  • 1 507
    • Zobrazit profil
    • E-mail
Re:Násobení matic a automatické přetypování
« Odpověď #54 kdy: 18. 09. 2015, 07:59:08 »
To by byl ten případ s (implicitním) přetypováním (operator SquareMatrix()), nicméně ani to mi neumožní napsat v C++ něco jako (m1*m2).trace() (na rozdíl od Swiftu).
Jo, v C++ by bylo třeba něco jako "square(m1*m2).trace()". To není zas tak zlé.

BTW umí nějaký (jiný) jazyk toto?

Kód: [Vybrat]
func *(x:Vector,y:Vector) -> Tensor {...}
func *(x:Vector,y:Vector) -> Double {...}
func *(x:Vector,y:Vector) -> AST {...}

let p:Double = a*b // scalar product
let p:Tensor = a*b // tensor product
let p:AST = a*b // AST

Docela praktické, hlavně ten AST.

Inkvizitor

Re:Násobení matic a automatické přetypování
« Odpověď #55 kdy: 18. 09. 2015, 08:35:55 »
Proč tam je RectangleMatrix?

V rámci zadání to nemá vliv, ale měl jsem potřebu tam dát tuhle třídu hlavně kvůli symetrii. "Nečtvercová" i čtvercová matice jsou pořád matice, pokud bych to měl brát takto, nedělal bych speciální třídu pro čtvercovou matici (jak jsem psal v prvním příspěvku v tomto tématu). Co mi ale hlavně vadí na prosté hierarchii Matrix -> SquareMatrix je skutečnost, že někdo může explicitně vytvořit instanci třídy Matrix a ta nebude umět operace čtvercové matice i přesto, že de facto zrovna čtvercová bude a to jenom proto, že někdo zkonstruoval instanci nepřesné třídy. Takhle je Matrix hezky abstraktní a tovární metoda udělá matici správné třídy hned od začátku. Samozřejmě, šlo by do konstruktorů SquareMatrix a RectangleMatrix dát i assert, ale dál jsem to rozvíjet nechtěl.

Inkvizitor

Re:Násobení matic a automatické přetypování
« Odpověď #56 kdy: 18. 09. 2015, 08:43:31 »
Pattern matching je jedno z možných řešení, ale také to je jen zakuklené přetypování.

Pattern matchingem proběhne detekce a přiznání se (s přetypováním) najednou a nemůže se stát, že zkontroluju omylem instanci na jednu třídu a pak se ji snažím přetypovat na úplně jinou s vyhozením výjimky při běhu. S pattern matchingem (typicky) nikdy nemusím používat potenciálně nebezpečnou operaci.

Radek Miček

Re:Násobení matic a automatické přetypování
« Odpověď #57 kdy: 18. 09. 2015, 08:57:49 »
To by byl ten případ s (implicitním) přetypováním (operator SquareMatrix()), nicméně ani to mi neumožní napsat v C++ něco jako (m1*m2).trace() (na rozdíl od Swiftu).
Jo, v C++ by bylo třeba něco jako "square(m1*m2).trace()". To není zas tak zlé.

BTW umí nějaký (jiný) jazyk toto?

Kód: [Vybrat]
func *(x:Vector,y:Vector) -> Tensor {...}
func *(x:Vector,y:Vector) -> Double {...}
func *(x:Vector,y:Vector) -> AST {...}

let p:Double = a*b // scalar product
let p:Tensor = a*b // tensor product
let p:AST = a*b // AST

Docela praktické, hlavně ten AST.

V C# lze dosáhnout v podstatě téhož tak, že násobení vrátí instanci MultiplicationResult a třída MultiplicationResult bude obsahovat implicitní konverzi na double a na Tensor. AST lze získat pomocí Expression - tj. násobení místo parametru typu T bude brát parametr typu Expression<T>.

Ve Scale se použije například (ve Scale bude asi hodně možností, jak dosáhnout téhož) trik analogický tomu s CanBuildFrom - tj. násobení bude mít implicitní parametr BuildResult[T] a vracet typ T. Takto definované násobení pak může kdokoliv (i cizí kód) rozšířit na libovolný návratový typ, případně předefinovat jeho chování pro určité návratové typy. S AST lze ve Scale pracovat pomocí maker.

JS

Re:Násobení matic a automatické přetypování
« Odpověď #58 kdy: 18. 09. 2015, 09:48:35 »
Diskuse se zase zvrtla, tak to shrňme. Jsou tři možnosti:

Vsechny tri moznosti jsou pretypovani, tam ci onde (ta treti je pretypovani po navratu z nasobeni matice). Jediny rozdil je, ze programatori treba v Pythonu chapou jeho typy jako navzajem ruzne, zatimco typovi teoretici to (nekdy) vnimaji jako jeden typ. Jak rikam, typy maji v programovani asi 3 ruzne vyznamy:

  • Umoznuji statickou typovou kontrolu
  • Indikuji zpusob polymorfismu
  • Definuji zpusob ulozeni dat v pameti

V tom tvem tretim pripade jenom kombinujes vyznam slova "typ" 1 a 2, coz je v poradku (protoze slovo typ je zavadejici), ale neni to zadna revoluce. V Haskellu se slovo typ pouziva pro 1. vyznam, v Pythonu pro 2. vyznam, v C pro 3. vyznam (prevazne). Ve vetsine modernejsich jazyku lze kontrolovat vsechny tri vyznamy pomoci ruznych mechanismu (napr. Haskell - typove tridy umoznuji delat vyznam 2).

(I kdyz co umoznuje delat v Haskellu vyznam 3 mi neni jasne. V dobre typovanych jazycich je vyznam 3 neco jako dependency injection, prijde mi, ze je tam nejaka dualita mezi vyznamem 2 a 3 - v podstate dualita mezi typovymi tridami a daty, nebo dualita mezi interfacem a implementaci.)

zboj

  • *****
  • 1 507
    • Zobrazit profil
    • E-mail
Re:Násobení matic a automatické přetypování
« Odpověď #59 kdy: 18. 09. 2015, 10:13:42 »
To by byl ten případ s (implicitním) přetypováním (operator SquareMatrix()), nicméně ani to mi neumožní napsat v C++ něco jako (m1*m2).trace() (na rozdíl od Swiftu).
Jo, v C++ by bylo třeba něco jako "square(m1*m2).trace()". To není zas tak zlé.

BTW umí nějaký (jiný) jazyk toto?

Kód: [Vybrat]
func *(x:Vector,y:Vector) -> Tensor {...}
func *(x:Vector,y:Vector) -> Double {...}
func *(x:Vector,y:Vector) -> AST {...}

let p:Double = a*b // scalar product
let p:Tensor = a*b // tensor product
let p:AST = a*b // AST

Docela praktické, hlavně ten AST.

V C# lze dosáhnout v podstatě téhož tak, že násobení vrátí instanci MultiplicationResult a třída MultiplicationResult bude obsahovat implicitní konverzi na double a na Tensor. AST lze získat pomocí Expression - tj. násobení místo parametru typu T bude brát parametr typu Expression<T>.

Ve Scale se použije například (ve Scale bude asi hodně možností, jak dosáhnout téhož) trik analogický tomu s CanBuildFrom - tj. násobení bude mít implicitní parametr BuildResult[T] a vracet typ T. Takto definované násobení pak může kdokoliv (i cizí kód) rozšířit na libovolný návratový typ, případně předefinovat jeho chování pro určité návratové typy. S AST lze ve Scale pracovat pomocí maker.

Jasně, tak to umí i C++. Ale umí to ještě nějaký jazyk v době kompilace? Benchmark by asi dopadl pro C# v tomto případě špatně.