2014-11-05 8 views
22

Chcę zaimplementować nieskończoną listę:Klasa obudowy Scala nie zezwala na parametry wywoławcze?

abstract class MyList[+T] 
case object MyNil extends MyList[Nothing] 
case class MyNode[T](h:T,t: => MyList[T]) extends MyList[T] 

//error: `val' parameters may not be call-by-name 

problemem jest call-by-name nie jest dozwolony.

Słyszałem, że to dlatego, że parametr konstruktora val lub var jest niedozwolony dla call-by-name. Na przykład:

class A(val x: =>Int) 
//error: `val' parameters may not be call-by-name 

Ale sprzeczność wynika, że ​​normalny parametr konstruktora jest nadal val mimo private. Na przykład:

class A(x: =>Int) 
// pass 

Więc pytanie:

  • Czy problem naprawdę o val lub var?
    • Jeśli to. Ponieważ punktem wywołania po nazwie jest odroczenie obliczeń, dlaczego nie można odłożyć obliczeń (lub inicjowania) od val lub var?
  • Jak obejść klasę cass, aby zaimplementować nieskończoną listę?
+4

Dla nieskończonej struktury danych, jaką wartość zapewnia cukier klasy przypadku? 'equals',' hashCode', 'toString' nie będzie działać. I nie jestem pewien, czego bym się spodziewał po "unapply". –

Odpowiedz

15

Nie ma sprzeczności: class A(x: => Int) jest równoważna class A(private[this] val x: => Int) i nie class A(private val x: => Int). private[this] oznacza wartość instancji-private, a modyfikator prywatny bez dalszych specyfikacji umożliwia uzyskanie dostępu do wartości z dowolnej instancji tej klasy.

Niestety, zdefiniowanie case class A(private[this] val x: => Int) jest niedozwolone. Zakładam, że tak jest, ponieważ klasy przypadków potrzebują dostępu do wartości konstruktora innych instancji, ponieważ implementują metodę equals.

Niemniej jednak, można realizować funkcje klasa przypadek nadadzą ręcznie:

abstract class MyList[+T] 

class MyNode[T](val h: T, t: => MyList[T]) extends MyList[T]{ 

    def getT = t // we need to be able to access t 

    /* EDIT: Actually, this will also lead to an infinite recursion 
    override def equals(other: Any): Boolean = other match{ 
    case MyNode(i, y) if (getT == y) && (h == i) => true 
    case _ => false 
    }*/ 

    override def hashCode = h.hashCode 

    override def toString = "MyNode[" + h + "]" 

} 

object MyNode { 
    def apply[T](h: T, t: => MyList[T]) = new MyNode(h, t) 
    def unapply[T](n: MyNode[T]) = Some(n.h -> n.getT) 
} 

Aby sprawdzić ten kod, można spróbować:

def main(args: Array[String]): Unit = { 
    lazy val first: MyNode[String] = MyNode("hello", second) 
    lazy val second: MyNode[String] = MyNode("world", first) 
    println(first) 
    println(second) 
    first match { 
    case MyNode("hello", s) => println("the second node is " + s) 
    case _ => println("false") 
    } 
} 

Niestety, nie wiem na pewno, dlaczego nazwani członkowie val i var są zabronieni. Istnieje jednak co najmniej jedno zagrożenie: pomyśl o tym, w jaki sposób klasy przypadków implementują toString; Metoda toString - metoda każdej wartości konstruktora jest wywoływana. Mogłoby to (iw tym przykładzie) prowadzić do wartości, które nazywają się nieskończenie. Można to sprawdzić, dodając metodę t.toString do MyNode za pomocą metody toString.

Edit: Po przeczytaniu komentarza Chris Martin: Realizacja equals będzie również stanowić problem, który jest prawdopodobnie bardziej dotkliwe niż realizacja toString (który jest stosowany głównie do debugowania) i hashCode (która doprowadzi jedynie do wyżej współczynniki kolizji, jeśli nie można wziąć parametru pod uwagę). Musisz uważnie przemyśleć, jak zaimplementować equals, aby było sensowne.

+2

Myślę, że po prostu zostawiłbyś 'equals' w taki sposób, w jaki był, z zastrzeżeniem, że będzie działać tylko dla list kończących. Tak działa 'Stream', prawda? 'Strumień (1) == Strumień (1)' jest "prawdziwy", ale 'Strumień.z (1) == Strumień.z (1)' nie zatrzymuje się. –

+0

@ChrisMartin Zależy od tego, w jaki sposób lista jest używana (bez cykli, jest w porządku), ale prawdopodobnie zaimplementuję bezpieczniejszą metodę "równości". Można na przykład przekazać zestaw już odwiedzonych instancji 'MyList' do wyspecjalizowanej' equalsMyList (other: MyList [T], visited: Set [MyList [T]]) '-metoda. Wyspecjalizowana metoda może następnie sprawdzić rekursję, sprawdzając, czy "to" jest już zawarte w 'odwiedzony', i zwraca w tym przypadku wartość true. Kryterium aborcji prawdopodobnie musiałoby sprawdzić ** tożsamość obiektu ** zamiast regularnej równości, lub możemy natknąć się na następną niekończącą się rekurencję. –

4

Nie znalazłem również przyczyny, dla których dokładne parametry nazwy są zabronione w klasach przypadków. Myślę, że wyjaśnienie powinno być dość skomplikowane i złożone. Ale Runar Bjarnason w swojej książce "Functional Programming in Scala" zapewnia dobre podejście do obsługi tej przeszkody. Używa pojęcia "thunk" wraz z memoizing. Oto przykład realizacji Stream:

sealed trait Stream[+A] 
case object Empty extends Stream[Nothing] 
case class Cons[+A](h:() => A, t:() => Stream[A]) extends Stream[A] 
object Stream { 
def cons[A](hd: => A, tl: => Stream[A]): Stream[A] = { 
    lazy val head = hd 
    lazy val tail = tl 
    Cons(() => head,() => tail) 
} 
def empty[A]: Stream[A] = Empty 
def apply[A](as: A*): Stream[A] = 
    if (as.isEmpty) empty else cons(as.head, apply(as.tail: _*)) 
} 
} 

Jak widać, zamiast zwykłej parametr-nazwy konstruktora danych klasa przypadku ich używać, co nazywają „pomyślał”, funkcję zero argumentów () => T. Następnie, aby uczynić to przezroczystym dla użytkownika, deklaruje on inteligentny konstruktor w obiekcie towarzyszącym, który pozwala podać parametry nazwy użytkownika i uczynić je zapamiętanymi.