# Wie manche ich das in Scala



## dmike (14. Feb 2010)

```
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 


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

Damit das geht muss ja denke ich  schreiben 


```
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?



```
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 (14. Feb 2010)

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)

```
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 (14. Feb 2010)

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?


----------



## Landei (15. Feb 2010)

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.


----------



## bygones (15. Feb 2010)

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 (16. Feb 2010)

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 
	
	
	
	





```
var y : FileSystemType = null
```
  und will aber eigentlich null vermeiden und lieber so etwas schreiben 
	
	
	
	





```
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 (16. Feb 2010)

dmike hat gesagt.:


> 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.
> 
> ...


Man kann auch schreiben 
	
	
	
	





```
var y : FileSystemType = _
```



> ... und will aber eigentlich null vermeiden und lieber so etwas schreiben
> 
> 
> 
> ...


Zuerst muss die Variable natürlich eine Option sein:

```
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

```
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

```
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

```
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.


----------



## dmike (16. Feb 2010)

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



```
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.


----------



## Landei (16. Feb 2010)

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 (16. Feb 2010)

Landei hat gesagt.:


> 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.





```
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


```
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.


----------



## dmike (16. Feb 2010)

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 


```
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.




```
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



```
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 (16. Feb 2010)

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 (18. Feb 2010)

Landei hat gesagt.:


> 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


----------

