Wie manche ich das in Scala

dmike

Bekanntes Mitglied
Code:
class File(filename : String) {
  def name = filename
}


class Directory(dirName :String, content : List[FilesystemType]) {
  
  def name = dirName

  def getContent = content
}


Ich möchte jetzt in Directory sowohl Files als auch andere Directories speichern können. Also

Code:
val dir = new Directory( "/", List(new File("file1"), new Directory(...), new File("file2"))  )

Damit das geht muss ja denke ich schreiben

Code:
class File(filename : String)  extends FilesystemType {
  
  override def name = filename

}

class Directory(dirName :String, content : List[FilesystemType]) extends FilesystemType {
  
  override def name = dirName

  override def getContent = content
}



Problem ist wie formuliere ich FilesystemType?


Code:
abstract class FilesystemType {
  def name() : String

  def getContent() : List[FilesystemType]
}


Nur für ein File Objekt macht der Aufruf getContent() keinen Sinn. Jedenfalls nicht wenn getContent eine List[FilesystemType] zurück gibt. Trotzdem wird File diese Schnittstelle implementieren müssen (denke ich) . Gibt's da nicht eine bessere Lösung für das Problem? Wie macht das normalerweise?
 

Landei

Top Contributor
Die "bequeme" Lösung wäre natürlich, bei Files eine leere Liste zurückzuliefern. Aber soll FileSystemType überhaupt getContent enthalten? Eigentlich nicht, denn das ist spezifisch für Directories. Normalerweise willst du sowieso ein unterschiedliches Verhalten von Files und Directories, und da würde sich Pattern Matching anbieten (wozu File und Directory aber besser case classes sein sollten). Ich würde das ganze so schreiben:

(ungetestet)
Code:
sealed trait FileSystemType {
   val name:String
}

case class File(name: String) extends FileSystemType

case class Directory(name:String, content: FileSystemType*) extends FileSystemType

//z.B. eine rekursive Suchfunktion
def search(fst: FileSystemType, found: File => Boolean): List[File] = fst match {
   case f:File => if (found(f)) List(f) else Nil
   case d:Directory => d.content.map(search(_)).flatten.toList
}
 

dmike

Bekanntes Mitglied
Super danke!

Jetzt hab ich aber folgendes Video auf you tube gesehen

""The Clean Code Talks -- Inheritance, Polymorphism, & Testing""
YouTube - "The Clean Code Talks -- Inheritance, Polymorphism, & Testing"

Da spricht er davon, dass man if's und switch/case in OO tunlichst vermeiden und besser durch polymorphes Verhalten ersetzen soll.. etwa ab @8:00 geht's los. @12:05 ist auch interessant.

Im Prinzip sind cases classes ja stark aufgepimpte switch case Anweisungen. Sollte man sie nun in einem OO Model verwenden oder nicht? Was meinst Du?
 
Zuletzt bearbeitet:

Landei

Top Contributor
Ich kann die Kritik an Fallklassen und Pattern Matching nicht so richtig nachvollziehen. Natürlich kann man jedes Feature missbrauchen. Wahrscheinlich kommt die Kritik von einem zu javazentrischen Verständnis. Aber Scala ist nicht Java: Enums werden meistens durch Fallklassen oder -Objekte ausgedrückt, die Methoden sind kürzer und bestehen oft nur aus einem einzigen Match-Ausdruck, es gibt weniger Schleifen und mehr Rekursion. Fallklassen und Pattern-Matching wirken in Scala "natürlich" und sind orthogonal zu anderen Features. Verglichen mit "polymorphen" Strategien wie dem Visitor-Pattern sind sie viel einfacher und verständlicher. Fallklassen dienen außerdem zur Implementierung Algebraischer Datentypen, die sich in funktionalen Sprachen schon lange als nützliches Konzept etabliert haben.

Hier ist Martin Oderskys Meinung dazu: In Defense of Pattern Matching

Im Übrigen übersehen die Kritker, dass Java selbst ein sehr spezialisiertes und eingeschränktes Pattern-Matching hat, nämlich bei catch-Blöcken. Ähnliches Verhalten müsste man in "normalen" Code mit if(ex instanceof XYException)-Kaskaden nachbilden.
 
Zuletzt bearbeitet:
B

bygones

Gast
es ist schon richtig, dass in der Java Welt man versuchten sollte if bedingungen durch polymorphismus zu ersetzen (wenn natuerlich moeglich....).

Wie aber Landei schon sagt ist das in der Scala Welt ein bisschen anders. Das hier verwendete Konstrukt der case Klassen ist in Java nicht bekannt.
 

dmike

Bekanntes Mitglied
Ok das macht Sinn. Wo vorhin das Visitor Pattern gefallen ist.. gibt es etwas vergleichbares wie Muster in dert funktionalen Welt? Also sowas wie ein GOF Buch für Functions?

Noch eine Scala-Frage: Es geht um den Typ Option.

In vielen Beispielen sehe ich den Option immer als result-Typ einer Funktion. Was ist aber mit der Eingabe für eine Funktion? Angenommen ich habe
Code:
var y : FileSystemType = null
und will aber eigentlich null vermeiden und lieber so etwas schreiben
Code:
var y : FileSystemType = Option(..)
um deutlich zu machen das y noch keinen eindeutigen Wert besitzt. Was natürlich so nicht geht in Scala. Aber durch was kann ich in Scala null ersetzen, so dass ich auch immer meineFunktion(y) aufrufen kann ohne, dass es zur NPE in meineFunktion kommt?
 

Landei

Top Contributor
Ok das macht Sinn. Wo vorhin das Visitor Pattern gefallen ist.. gibt es etwas vergleichbares wie Muster in dert funktionalen Welt? Also sowas wie ein GOF Buch für Functions?

Noch eine Scala-Frage: Es geht um den Typ Option.

In vielen Beispielen sehe ich den Option immer als result-Typ einer Funktion. Was ist aber mit der Eingabe für eine Funktion? Angenommen ich habe
Code:
var y : FileSystemType = null
Man kann auch schreiben
Code:
var y : FileSystemType = _

... und will aber eigentlich null vermeiden und lieber so etwas schreiben
Code:
var y : FileSystemType = Option(..)
um deutlich zu machen das y noch keinen eindeutigen Wert besitzt. Was natürlich so nicht geht in Scala. Aber durch was kann ich in Scala null ersetzen, so dass ich auch immer meineFunktion(y) aufrufen kann ohne, dass es zur NPE in meineFunktion kommt?
Zuerst muss die Variable natürlich eine Option sein:
Code:
var y :Option[FileSystemType] = None
...
y = Some(File("xyz"))
Dann ist die Frage, wie sich deine Funktion im Fall von None verhalten soll.
1) Es ist immer ein Fehler
Code:
def f(fs:Option[FileSystemType]) = fs match {
   case Some(x) => //tu was
   case None => error("File does not exist")
}

2) Es gibt einen sinnvollen Rückgabewert
Code:
def f(fs:Option[FileSystemType]):Boolean = fs match {
   case Some(x) => testFunction(x)
   case None => false
}

3) Du willst die Option "durchschleifen", damit sie in der nächsten Verarbeitungsstufe verfügbar ist
Code:
def f(fs:Option[FileSystemType]):Option[Date] = fs.map(_.date)

Übrigens empfehle ich für den Umgang mit Option, erst mal Tony Morris' Option Cheat Sheet durchzulesen.
 
Zuletzt bearbeitet:

dmike

Bekanntes Mitglied
Danke, schön, dann kann ich in Zukunft besser sowas schreiben:


Code:
import java.io._


/* 
 *
 * 
 * scala> val filesHere = (new File("/boot")).listFiles
 * filesHere: Array[java.io.File] = Array(/boot/kernel26-fallback.img, /boot/kernel26.img, 
 * /boot/vmlinuz26, /boot/lost+found, /boot/grub, /boot/System.map26)
 *
 * /boot/lost+found ist als normaler User nicht zugaenglich und listFiles() liefert daher
 * anstelle von /boot/lost+found  null
 *
 */

 // Liste mit files in /boot, eins davon ist null wg. /boot/lost+found
val filesHere = (new File("/boot")).listFiles

var npeSecuredFiles : Option[Array[File]] = None

for (file <- filesHere) {
  npeSecuredFiles = Some(file.listFiles)
  npeSecuredFiles match {
      case Some(null) => if (file.isDirectory) println("Some-ERR!: " + file) else println("OK: " + file)
      case None => println("None-ERR!: " + file)
      case _ => println("OK: " + file) ; file.listFiles
  }
}


scala test.scala 
OK: /boot/kernel26-fallback.img
OK: /boot/kernel26.img
OK: /boot/vmlinuz26
Some-ERR!: /boot/lost+found
OK: /boot/grub
OK: /boot/System.map26

Jetzt muss ich nur noch verstehen, wie ich die gültigen files aus der Schleife nach Außen bekomme...

und die Faelle filesHere == null und filesHere enthält null ist auch noch offen.
 
Zuletzt bearbeitet:

Landei

Top Contributor
An dieser Stelle macht Option wenig Sinn. listFiles sollte im Idealfall eine Option zurückliefert (oder einfach eine leere Liste, wenn der Zugriff nicht erlaubt ist). Wenn du nur eine Variante hast, die null zurückliefert, kannst du das auch direkt behandeln, es vorher in Option "einzuwickeln" bringt nicht viel, wenn du es sowieso gleich im nächsten Schritt testest und in die einzelnen Fälle aufsplittest.
 

dmike

Bekanntes Mitglied
An dieser Stelle macht Option wenig Sinn. listFiles sollte im Idealfall eine Option zurückliefert (oder einfach eine leere Liste, wenn der Zugriff nicht erlaubt ist). Wenn du nur eine Variante hast, die null zurückliefert, kannst du das auch direkt behandeln, es vorher in Option "einzuwickeln" bringt nicht viel, wenn du es sowieso gleich im nächsten Schritt testest und in die einzelnen Fälle aufsplittest.


Code:
 for (file <- filesHere) {

      if (file==null) {
        npeSecuredFiles = None
      } else {
        npeSecuredFiles = Some(file.listFiles)
      }

      npeSecuredFiles match {
        case Some(null) => if (file.isFile) fs ::= new File(file.getAbsoluteFile.toString) 
        case None => println("None-ERR!: " + file)
        case _ => var dir = new Directory(file.getAbsoluteFile.toString); dir.list; fs ::= dir
      }

Doch schon, weil ich auch den Fall file == null abfangen muss und ich ja kein break und auch kein continue machen darf. Deswegen passt es ganz gut, dass ich null auf None mappen kann.

Oder sehe ich das falsch?

Ok, ich kann natürlich auch schreiben

Code:
 for (file <- filesHere) {
      if (file!=null) {
         if (file.listFiles !=null && file.isFile ) fs ::= new File(file.getAbsoluteFile.toString) 
         else {
             var dir = new Directory(file.getAbsoluteFile.toString); dir.list; fs ::= dir
         }
      )
}

Dann sollte ich besser den Zugriff auf listFiles und filesHere wrappen und eine leere Liste oder None zurückgeben wenn listFiles==null bzw. filesHere==null ist. Stimm hast Recht das wäre besser.
 
Zuletzt bearbeitet:

dmike

Bekanntes Mitglied
Soweit kann ich ein echtes Verzeichnis lesen - pseudorekursiv - und auf diesen Verzeichnisbaum eine Suchanfrage stellen (finde alle mp3 Dateien).

Erstmal der Code mit dem Verzeichnis

Code:
import java.io.{File => JavaFile}

sealed trait FileSystemType


/**
 *
 *
 */
case class File(name: String) extends FileSystemType


/**
 *
 *
 */
case class Directory(name: String) extends FileSystemType {

  def read: List[FileSystemType] = {

    for (file <- filesHere) {
      if (file.isFile) {
        fs ::= File(pathName(file))
      } else {
        val nextDir = Directory(pathName(file))
        nextDir.read
        fs ::= nextDir
      }
    }
    fs
  }


  def list: List[FileSystemType] = fs

  def filesHere = preventHavoc((new JavaFile(name)).listFiles)

  
  
  //*********************************************************
  
  private def preventHavoc(filesHere: Array[JavaFile]): Array[JavaFile] = {
     // prevent any NPE hazards due to Java's file IO handling
    if (filesHere==null) Array() else filesHere.filter(_!=null)
  }

  private def pathName(file : JavaFile) = file.getAbsoluteFile.toString
  private var fs: List[FileSystemType] = Nil

}


case class DirectoryMockup(name: String,list: FileSystemType*) extends FileSystemType

Ich musste leider eine extr DirectoryMockup einführen um die Testfälle zu unterstürzen.



Code:
class ShouldFindAllMP3s {

  var rootDir : FileSystemType = null

  @Before
  def setup() {

    rootDir = DirectoryMockup("/", File("thing.txt"), DirectoryMockup("tmp", File("music.mp3"),DirectoryMockup("music", File("music4.mp3"))), File("music2.mp3"))

    //rootDir = Directory("/", File("thing.mp3"))
  }


  @Test
  def shouldFindAllMP3sInMockup {

    val fs = new FileSystem(rootDir)

    val mp3Finder = (x: File) => x.name.toLowerCase.endsWith("mp3")

    val files = fs.search(mp3Finder)

    assert(files.size > 0, "Must have min 1 mp3 file!")
    assert(files.size == 3, "Must have 3 mp3 files!")
  }


  @Test
  def shouldFindAllFilesInFilesystem {

    val dir = new Directory("/home/dev/test")

    dir.read

    val fs = new FileSystem(dir)

    val mp3Finder = (x: File) => x.name.toLowerCase.endsWith("mp3")

    val files = fs.search(mp3Finder)

    assert(files.size > 0, "Must have 0 mp3 files!")
    assert(files.size == 2, "Must have 2 mp3 files!")
  
  }  


}

Beim letzten Testcase sieht man, dass man nach dir = new Directory(...) erst noch dir.read aufrufen muss. Auch Mist. Eigentlich muss das automatisch passieren. Ich weiß aber nicht wie man an den Konstruktor einer case klasse heran kommt. In Java hätte ich read() vom Ctor aus aufgerufen. Andererseits erkenn man in der Methode def read in der Klasse Directory, dass der Call rekursiv erfolgt. Was man sonst vlt. übersehen würde. Eine richtige Lösung ist das aber trotzdem nicht.


Zum Schluss die Klasse auf die die Abfrage losgelassen wird. Sie "operiert" auf dem Directory. Das kann das richtige Directory sein oder ein Mockup


Code:
class FileSystem (val root : FileSystemType) {

  def search(finder: File => Boolean): List[File] = {
    search(root, finder)
  }

  def search(fst: FileSystemType, finder: File => Boolean): List[File] = fst match {

    
    case f:File => if (finder(f)) List(f) else Nil

    case d:Directory => d.list.map(search(_,finder)).flatten.toList

    case m:DirectoryMockup => m.list.map(search(_,finder)).flatten.toList
  }
  
}


Das dumme ist aber, dass DirectoryMockup, weil sie nun mal auch zum sealed trait gehört, mit in den code aufgenommen werden muss. Wüsste jetzt nicht wie ich das umgehen kann.
 

Landei

Top Contributor
Schreib einfach "val read.." statt "def read...", oder besser noch "lazy val read..." (damit die Unterverzeichnisse erst dann ausgelesen werden, wenn wirklich jemand drauf zugreift). Was in der Klasse zwischen { und } steht, ist der (Haupt-)Konstruktor, auch bei case classes.

Über das Mock-Problem habe ich noch nicht nachgedacht...
 

dmike

Bekanntes Mitglied
Schreib einfach "val read.." statt "def read...", oder besser noch "lazy val read..." (damit die Unterverzeichnisse erst dann ausgelesen werden, wenn wirklich jemand drauf zugreift). Was in der Klasse zwischen { und } steht, ist der (Haupt-)Konstruktor, auch bei case classes.

Über das Mock-Problem habe ich noch nicht nachgedacht...

Das Mock Problem bekomme ich noch raus . Ansonsten danke für die zahlreichen Tipps. Das gibt eine Danke += 1 ;)
 
Ähnliche Java Themen
  Titel Forum Antworten Datum
K Unterschied funktionial <-> OO anhand von Scala <-> Java JVM Sprachen: Kotlin, Scala, Groovy, Jython... 5
M Experten für Scala-Play- Programmierung gesucht!! JVM Sprachen: Kotlin, Scala, Groovy, Jython... 3
M Scala-Programm mit Netbeans compilieren JVM Sprachen: Kotlin, Scala, Groovy, Jython... 1
M Suche Scala Entwickler (Umsteiger [JAVA]) für Zusammenarbeit an privatem Projekt JVM Sprachen: Kotlin, Scala, Groovy, Jython... 7
R Frage zu Scala Code JVM Sprachen: Kotlin, Scala, Groovy, Jython... 2
Landei Scala Scala 2.10 RC JVM Sprachen: Kotlin, Scala, Groovy, Jython... 3
schlingel Scala Schulung - Gratis vom Scala-Schöpfer JVM Sprachen: Kotlin, Scala, Groovy, Jython... 2
Landei Scala Scala-Kritik JVM Sprachen: Kotlin, Scala, Groovy, Jython... 151
Spin Scala Eclipse IDE JVM Sprachen: Kotlin, Scala, Groovy, Jython... 7
Spin Funktionen vs Methods in Scala JVM Sprachen: Kotlin, Scala, Groovy, Jython... 9
Landei Scala Freies eBook "Scala for the impatient" JVM Sprachen: Kotlin, Scala, Groovy, Jython... 2
Spin Arithmetik in Scala JVM Sprachen: Kotlin, Scala, Groovy, Jython... 32
0x7F800000 Numerik in Scala (Performance) JVM Sprachen: Kotlin, Scala, Groovy, Jython... 14
Spin Scala MenuListener JVM Sprachen: Kotlin, Scala, Groovy, Jython... 5
Spin Scala in Eclipse will nicht. JVM Sprachen: Kotlin, Scala, Groovy, Jython... 15
Landei Scala Deutsches Scala-Tutorial JVM Sprachen: Kotlin, Scala, Groovy, Jython... 3
B Scala oder Clojure JVM Sprachen: Kotlin, Scala, Groovy, Jython... 6
Landei Scala "Programming in Scala" - erste Ausgabe kostenlos JVM Sprachen: Kotlin, Scala, Groovy, Jython... 1
W Scala *.Scala to *.jar JVM Sprachen: Kotlin, Scala, Groovy, Jython... 6
H Scala und Aspekte JVM Sprachen: Kotlin, Scala, Groovy, Jython... 4
S Scala Klasse.class in Scala? JVM Sprachen: Kotlin, Scala, Groovy, Jython... 4
B Scala Scala und Netbeans GUI Editor JVM Sprachen: Kotlin, Scala, Groovy, Jython... 15
S Scala: Parser und Lexical JVM Sprachen: Kotlin, Scala, Groovy, Jython... 2
S Scala: Static - Konstruktor?? JVM Sprachen: Kotlin, Scala, Groovy, Jython... 5
G Scala IDE JVM Sprachen: Kotlin, Scala, Groovy, Jython... 18
A Scala und J2ME JVM Sprachen: Kotlin, Scala, Groovy, Jython... 2
S Scala Fragen zu Scala JVM Sprachen: Kotlin, Scala, Groovy, Jython... 21
D (Mathe-) Vektoren in Scala JVM Sprachen: Kotlin, Scala, Groovy, Jython... 4
Landei Scala im Kommen :-) JVM Sprachen: Kotlin, Scala, Groovy, Jython... 4

Ähnliche Java Themen

Neue Themen


Oben