2012-11-15 12 views
16

W Haskell, uważam, że możliwe jest alias typu w taki sposób, że kompilator nie zezwala na odniesienia między typem aliasingu a typem niesiasnym. Według this stack overflow question, można użyć Haskell za newtype tak:Czy w ramach Scala możliwe jest alias typu, ale zabronione jest wykorzystywanie krzyżowe aliasów/nie-aliasów, takich jak Haskell?

newtype Feet = Feet Double 
newtype Cm = Cm Double 

gdzie Feet i Cm będą zachowywać się jak wartości podwójnymi, ale próbuje pomnożyć wartość Feet a wartość Cm spowoduje błąd kompilatora.

EDYCJA: Ben wskazał w komentarzach, że powyższa definicja w Haskell jest niewystarczająca. Feet i Cm będą nowymi typami, dla których nie będzie zdefiniowanych żadnych funkcji. Robi trochę więcej badań, okazało się, że będzie działać następuje:

newtype Feet = Feet Double deriving (Num) 
newtype Cm = Cm Double deriving (Num) 

To tworzy nowy typ, który wynika z istniejącego Num typu (wymaga użycia przełącznika: -XGeneralizedNewtypeDeriving). Oczywiście te nowe typy będą jeszcze bardziej wartościowe, wynikające z innych typów, takich jak Show, Eq itp., Ale jest to minimum wymagane do prawidłowej oceny Cm 7 * Cm 9.

Zarówno Haskell i Scala mieć type, który po prostu aliasy istniejącego typu i pozwala kod bezsensowny takie jak ten przykład w Scala:

type Feet = Double 
type Cm = Double 

val widthInFeet: Feet = 1.0 
val widthInCm: Cm = 30.48 

val nonsense = widthInFeet * widthInCm 

def getWidthInFeet: Feet = widthInCm 

robi Scala mieć newtype odpowiednik, przy założeniu, że robi to, co myślę, robi?

+0

To nie jest tak, jak 'newtype' działa w Haskell. Tworzy ** nowy typ **, który domyślnie nie ma zdefiniowanych żadnych funkcji. Zatem wartości 'Feet' i' Cm' w twoich przykładach nie będą mogły być pomnożone, dopóki nie zaimplementujesz dla nich mnożenia. Typy zadeklarowane z 'newtype' będą * reprezentowane * identycznie do typu zawijanego, co oznacza, że ​​istnieje zerowy koszt wykonania implementacji operacji na' newtype' przez proste rozpakowanie i przejście do operacji na typie zawiniętym. Ale to jest naprawdę optymalizacja, nieistotna dla tego, co oznacza "typ". – Ben

+0

Dzięki za informację, zaktualizowałem pytanie, aby to odzwierciedlić. Nadal jest znawcą Haskella, choć bardzo chce go poprawić. =) – rybosome

+6

Należy zauważyć, że dodając Cm do Cm, aby uzyskać Cm ma sens, pomnożenie Cm przez Cm dostarczy Ci Cm², który mierzy obszar, a nie długość. Używanie systemu typu Haskell do śledzenia jednostek może być dość trudne do zrobienia. – shachaf

Odpowiedz

10

Tak, używasz czegoś znanego jako Unboxed Tagged Types w scala.

ten sposób oznaczone są zdefiniowane:

type Tagged[U] = { type Tag = U } 
type @@[T, U] = T with Tagged[U] 

To pozwala zrobić coś takiego

sealed trait Feet 

def Feet[A](a: A): A @@ Feet = Tag[A, Feet](a) 
Feet: [A](a: A)[email protected]@[A,Feet] 

scala> val mass = Feet(20.0) 
mass: [email protected]@[Double,Feet] = 20.0 

scala> 2 * mass 
res2: Double = 40.0 

również dodać CM

sealed trait CM 

def CM[A](a: A): A @@ CM = Tag[A, CM](a) 
CM: [A](a: A)[email protected]@[A,CM] 

scala> val mass = CM(20.0) 
mass: scala[email protected]@[Double,CM] = 20.0 

Jeśli chcesz ograniczyć namnażanie do tylko stóp, możesz napisać funkcję mnożenia typu typeclass

trait Multiply[T] { self => 
    def multiply(a: T, b: T): T 
} 
implicit val footInstance = new Multiply[Feet] { 
    def multiply(a: Feet, b: Feet): Feet = Feet(a * b) 
} 
implicit val cmInstance = new Multiply[CM] { 
    def multiply(a: CM, b: CM): CM = CM(a * b) 
} 

def multiply[T: Multiply](a: T, b: T): T = { 
    val multi = implicitly[Multiply[T]] 
    multi.multiply(a,b) 
} 

można wtedy zrobić

multiply(Feet(5), Feet(10)) // would return Feet(50) 

jest to najlepszy Scala może zrobić

Aby dowiedzieć się więcej o pudełkowej typu sprawdzeniu http://eed3si9n.com/learning-scalaz-day3

+0

Instancje klasy typów nie działają. 'Stopy' lub 'CM' nie mają metody' * '. – sschaef

+0

Wiem, czy przeczytałeś ostatnią część. Możesz też pseudonim mnożyć jako ** lub coś podobnego. –

+2

Jest to zdecydowanie zbyt ciężki przypadek, w którym po prostu potrzebujesz cholernego 'type Foo = Bar' i kompilator faktycznie wymusza" Foo "właściwie nie powierzchownie. –

13

Innym rozwiązaniem byłoby wykorzystanie klasy wartości. Tworzą one wrapper wokół bazowego typu, który jest konwertowany na bezpośredni dostęp do typu surowego podczas kompilacji, a metody na klasie są konwertowane na statyczne wywołania w powiązanym obiekcie towarzyszącym.Na przykład:

class CM(val quant : Double) extends AnyVal { 
    def +(b : CM) = new CM(quant + b.quant) 
    def *(b : Int) = new CM(quant * b) 
} 
+0

Droga prostsza niż zaakceptowana odpowiedź! Ale nadal niezupełnie czysty, ponieważ mógłby ... –

+0

niejawny def cm_from_quant (quant: Double): CM = nowy CM (id) –

1

można użyć NewType z biblioteki scala-newtype!

wtyczka Shameless: Jestem autorem scala-newtype

https://github.com/estatico/scala-newtype

ten łączy pomysły z Scalaz i bezkształtne, jak również wprowadza pomysły prosto z Haskell (jak GeneralizedNewTypeDeriving).

Oto, jak może wyglądać twój kod za pomocą nowego typu. Podamy zarówno różne typy o nazwach Feet, jak i Cm, aby wyprowadzić klasę typu Numeric opartą na klasie Double (która jest automatycznie wykonywana przez deriving).

Możemy wtedy użyć metody rozszerzenie świadczonych przez Numeric.Implicits -

object Example { 

    type Feet = Feet.Type 
    object Feet extends NewType.Default[Double] { 
    implicit val num: Numeric[Type] = deriving 
    } 

    type Cm = Cm.Type 
    object Cm extends NewType.Default[Double] { 
    implicit val num: Numeric[Type] = deriving 
    } 

    val widthInFeet = Feet(1.0) 
    val widthInCm = Cm(30.48) 

    import Numeric.Implicits._ 

    // Does not compile: 
    // val nonsense = widthInFeet + widthInCm 

    // Compiles! 
    val doubleWidthInFeet: Feet = widthInFeet + widthInFeet 
} 

jednak użyć * na przykład, a nie chcemy Feet * Feet = Feet jak powinno być naprawdę Feet * Feet = Feet², więc dodajmy FeetSq wpisać do reprezentowania że i zdefiniować własne operacje są bardziej bezpieczne niż typ Numeric -

object Example { 

    type Feet = Feet.Type 
    object Feet extends NewType.Default[Double] { 
    implicit final class Ops(val self: Feet) extends AnyVal { 
     def +(other: Feet) = Feet(self.repr + other.repr) 
     def -(other: Feet) = Feet(self.repr - other.repr) 
     def *(other: Feet) = FeetSq(self.repr * other.repr) 
    } 
    } 

    type FeetSq = FeetSq.Type 
    object FeetSq extends NewType.Default[Double] 

    type Cm = Cm.Type 
    object Cm extends NewType.Default[Double] 

    val widthInFeet = Feet(1.0) 
    val widthInCm = Cm(30.48) 

    // Does not compile: 
    // val nonsense = widthInFeet * widthInCm 

    // Compiles! 
    val squareFeet: FeetSq = widthInFeet * widthInFeet 
} 

Tutaj używasz implicit final class Ops, aby zdefiniować metody na naszym nowym typie. Ta klasa jest eliminowana podczas kompilacji, więc w czasie wykonywania kończymy wywoływanie metod rozszerzania z obiektu Ops.

Powiązane problemy