2015-11-20 18 views
1

我有一些代碼,濫用Double在幾乎相同的方式爲String被濫用爲goto語句爲無類型的類型。以這種方式使用Double會導致很多細微的和/或隱藏的問題,通常由於有害的運行時錯誤而出現。這兩種方法是什麼樣子現在:使用squant庫,如何爲一種方法指定特定的度量單位?

object Geospatial { 
    def calculateDistance(
    coordinate1Longitude: Double, 
    coordinate1Latitude: Double, 
    coordinate2Longitude: Double, 
    coordinate2Latitude: Double 
): (Double, Double, Double) = { 
    //code encapsulated here only works with meters and radians 
    //returns (distance in meters, initial bearing in zero-based radians, final bearing in zero-based radians) 
    ??? 
    } 

    def calculateCoordinate(
    coordinate1Longitude: Double, 
    coordinate1Latitude: Double, 
    angle: Double, 
    distance: Double 
): (Double, Double) = { 
    //code encapsulated here only works with meters and radians 
    //returns angle in radians for longitude and latitude 
    ??? 
    } 
} 

你可以想像,如果客戶端調用這些方法之一,並沒有正確地轉換爲米,弧度和/或也忘的方法是以米和弧度返回值,客戶端將得到不正確的結果。

所以,我想大大提高上述方法的類型安全性;即,如果/當客戶端試圖調用這些方法中的任何一種時,客戶端會收到編譯時錯誤,而這些方法所傳遞的類型並不完全匹配這些方法需要的類型。本着這種精神,我已經重寫方法(和它們的上下文)更類型化(但仍不足以):

object Geospatial { 
    type AngleRadiansCentered = Double 
    //angle's range is restricted to [-Math.PI until Math.PI] 
    type AngleRadiansPositive = Double 
    //angle's range is restricted to [0.0d until (Math.PI * 2.0d)] 

    type LongitudeRadians = AngleRadiansCentered 
    type LatitudeRadians = AngleRadiansCentered //angle's range must be _further_ restricted to -(Math.PI/2.0d) until (Math.PI/2.0d) 

    def calculateDistance(
    coordinate1: (Longitude, Latitude), 
    coordinate2: (Longitude, Latitude) 
): (Meters, AngleRadiansPositive, AngleRadiansPositive) = { 
    //Legacy code encapsulated here only works with meters and radians 
    //returns (distance, initial bearing, final bearing) 
    ??? 
    } 

    def calculateCoordinate(
    coordinate1: (Longitude, Latitude), 
    bearing: AngleRadiansPositive, 
    distance: Meters 
): (Longitude, Latitude) = { 
    //Legacy code encapsulated here only works with meters and radians 
    ??? 
    } 
} 

正如我已經工作這個問題,我最近發現了squants庫。我想我想用squants來重寫上面的代碼。然而,在花了一個小時左右的時間閱讀稀少的文檔(至少在我的上下文中是相關的例子)之後,我無法在如何將它應用於這個問題上做出任何合理的飛躍。例如,如何將其指定爲方法參數,它只接受Meters(而不是更通用的Length)。 IOW,我正在尋找類型安全性,而不是類型之間的轉換(儘管這是我將在這些方法之外進行的)。

我找不到任何squants示例代碼snippits,從中我可以得到我所需要的。我並沒有要求完整的解決方案。我只需要指出正確的方向。首先,我需要知道squants是否是真正適合使用的API。然後,如果是這樣,我需要足夠的幫助將我推向正確的大方向,這樣我才能找出剩餘的解決方案空間。

我確實打算至少使用案例類來替換所有Double的實例。然而,在我這樣做之前,我想知道我是否可以通過squants圖書館來做到這一點。

任何有關這方面的指導將不勝感激。

+2

爲什麼不接受任何'Length',然後使用['toMeters'方法(https://github.com/garyKeorkunian將其轉換爲'Meter' /squants/blob/a4e3cdc6a39d5b4a4fa7f0cdede065fd32d762ab/shared/src/main/scala/squants/space/Length.scala#L71)? –

+0

我曾考慮過這個問題,但無法看到如何保證客戶端知道該功能需要明確預計米和弧度。然而,現在我已經看到了阿爾瓦羅的回答,我想我有點明白你的建議可能如何工作。 – chaotic3quilibrium

回答

5

我經歷了同樣的過程,這就是我發現的:至少在本質上,這個隱喻庫不會給你正好你正在尋找的東西。它提供的類型安全性,特別是指不混合不同類型的數量,但同一維度內的實際單位並不重要。

這就是說,在使用它一段時間後,我意識到它需要的方法實際上是我需要的方法。您仍然可以獲得重要的類型安全性:將值保留在維度中。並且在同一維度內混合不同的單位仍然安全,因爲您創建Length單位的方式是使用「構造函數」之一,如MetersCentimeter。內部表示應該不重要。您正在使用Length(重要部分)。如果您想要原始Meter(保存到數據庫?),那麼請在當時撥打toMeters。這樣做不會造成類型安全方面的損失。

+0

Tysvm爲您的答案。我想我明白你在說什麼。但是,爲了確保我「重構」了上述代碼,以便在添加域限制的同時併入您所說的內容。當你有一刻時,你會介意看看它,讓我知道我是否接近。任何反饋意見,將不勝感激。代碼如下:http://pastebin.com/1pYrXTrC – chaotic3quilibrium

+1

@ chaotic3quilibrium該代碼看起來正確。我可能不會「轉換」爲'require'檢查:'Radians(0.0d)<= angle)&&(angle

+0

太棒了!我完全同意。我現在要用我的代碼在pastebin.com上添加一個答案(但是根據您的建議進行修改)只是爲了完整性。我將選擇你的答案,因爲你的方向使我能夠看到如何創建squant代碼。 Tysvm! – chaotic3quilibrium

2

感謝Alvaro Carrasco's answer,我能看到我的使用squants庫最初的做法是略微傾斜(0​​,並已定)。下面以具體的解決方案結束什麼看起來像:

import squants.space.{Radians, Meters} 
import squants.{Angle, Length} 

object Geospatial { 
    case class Longitude(angle: Angle) { 
    require(
     (Radians(-Math.PI) <= angle) && (angle < Radians(Math.PI)), 
     "angle.inRadians must be greater than or equal to -Math.PI and less than Math.PI" 
    ) 
    } 
    case class Latitude(angle: Angle) { 
    require(
     (Radians(-(Math.PI * 0.5d)) <= angle) && (angle < Radians(Math.PI * 0.5d)), 
     "angle.inRadians must be greater than or equal to -(Math.PI * 0.5d) and less than (Math.PI * 0.5d)" 
    ) 
    } 
    case class Distance(length: Length) { 
    require(
     Meters(0.0d) <= length, 
     "length.inMeters must be greater than or equal to 0.0d" 
    ) 
    } 
    case class Bearing(angle: Angle) { 
    require(
     (Radians(0.0d) <= angle) && (angle < Radians(Math.PI * 2.0d)), 
     "angle.inRadians must be greater than or equal to 0.0d and less than (Math.PI * 2.0d)" 
    ) 
    } 

    case class Coordinate(longitude: Longitude, latitude: Latitude) 

    def calculateDistance(
    coordinate1: Coordinate, 
    coordinate2: Coordinate 
): (Distance, Bearing, Bearing) = { 
    def calculateDistanceUsingLegacyCodeRifeWithDoubles(
     coordinate1LongitudeInRadians: Double, 
     coordinate1LatitudeInRadians: Double, 
     coordinate2LongitudeInRadians: Double, 
     coordinate2LatitudeInRadians: Double 
    ): (Double, Double, Double) = { 
     //Legacy code encapsulated here only works with meters and radians 
     //returns (distance, initial bearing, final bearing) 
     (1.0d, 1.0d, 2.0d) //TODO: replace with real calculation results 
    } 
    val (coordinate1InRadians, coordinate2InRadians) = (
     (coordinate1.longitude.angle.toRadians, coordinate1.latitude.angle.toRadians), 
     (coordinate2.longitude.angle.toRadians, coordinate2.latitude.angle.toRadians) 
    ) 
    val (distanceInMeters, bearingInitialInRadians, bearingFinalInRadians) = 
     calculateDistanceUsingLegacyCodeRifeWithDoubles(
     coordinate1InRadians._1, 
     coordinate1InRadians._2, 
     coordinate2InRadians._1, 
     coordinate2InRadians._2 
    ) 
    (
     Distance(Meters(distanceInMeters)), 
     Bearing(Radians(bearingInitialInRadians)), 
     Bearing(Radians(bearingFinalInRadians)) 
    ) 
    } 

    def calculateCoordinate(
    coordinate1: Coordinate, 
    bearingInitial: Bearing, 
    distance: Distance 
): Coordinate = { 
    def calculateCoordinateUsingLegacyCodeRifeWithDoubles(
     coordinate1Longitude: Double, 
     coordinate1Latitude: Double, 
     bearingInitialInRadians: Double, 
     distanceInMeters: Double 
    ): (Double, Double) = { 
     //Legacy code encapsulated here only works with meters and radians 
     //returns (longitude, latitude) 
     (-1.0d, 1.0d) //TODO: replace with real calculation results 
    } 
    val (coordinate1InRadians, bearingInitialInRadians, distanceInMeters) = (
     (coordinate1.longitude.angle.toRadians, coordinate1.latitude.angle.toRadians), 
     bearingInitial.angle.toRadians, 
     distance.length.toMeters 
    ) 
    val (longitude, latitude) = 
     calculateCoordinateUsingLegacyCodeRifeWithDoubles(
     coordinate1InRadians._1, 
     coordinate1InRadians._2, 
     bearingInitialInRadians, 
     distanceInMeters 
    ) 
    Coordinate(Longitude(Radians(longitude)), Latitude(Radians(latitude))) 
    } 
} 
相關問題