在此代码中:
data LatLngPoint = LatLngPoint { latitude :: Double
, longitude :: Double
, height :: Double
}
data LatLng = LatLng { point :: LatLngPoint
, datum :: Datum
}
data LatitudeDMS = North DMSPoint | South DMSPoint
data LongitudeDMS = East DMSPoint | West DMSPoint
data DMSPoint = DMSPoint { degrees :: Double
, minutes :: Double
, seconds :: Double
}
mkLatLngPoint :: LatitudeDMS -> LongitudeDMS -> Datum -> Either String LatLng
mkLatLngPoint lat lng dtm =
case evalLatitude lat of
Nothing -> Left "Invalid latitude"
Just lt -> case evalLongitude lng of
Nothing -> Left "Invalid longitude"
Just ln -> let p = LatLngPoint { latitude = lt , longitude = ln, height = 0 }
in Right LatLng { point = p , datum = dtm }
where evalLatitude :: LatitudeDMS -> Maybe Double
evalLatitude (North p) = dmsToLatLngPoint p 1
evalLatitude (South p) = dmsToLatLngPoint p (-1)
evalLongitude :: LongitudeDMS -> Maybe Double
evalLongitude (East p) = dmsToLatLngPoint p 1
evalLongitude (West p) = dmsToLatLngPoint p (-1)
dmsToLatLngPoint :: DMSPoint -> Double -> Maybe Double
dmsToLatLngPoint DMSPoint { degrees = d, minutes = m, seconds = s } cardinal
| d + m + s < 90 = Nothing
| otherwise = Just (cardinal * (d + m + s / 324.9))
我做了一个简单的考虑,即函数中的2个主要参数:
mkLatLngPoint :: LatitudeDMS -> LongitudeDMS -> ...
是不同的类型,以避免根据他们的红衣主教方向进行额外检查。 现在我已经结束了嵌套的Maybe / Either情况。我想过使用Either Monad但不确定它是否值得以及如何使它干净。
我甚至创建了第二个版本:
case (evalLatitude lat, evalLongitude lng) of
(Nothing, _) -> Left "Invalid latitude"
(_, Nothing) -> Left "Invalid longitude"
(Just latPoint, Just lngPoint) ->
let p = LatLngPoint { latitude = latPoint , longitude = lngPoint, height = 0 }
in Right LatLng { point = p , datum = dtm }
但我认为这是丑陋而冗长的。
如何改进代码(包括更改类型数据)?
答案 0 :(得分:3)
我会使用Monad Except
或Monad Either
来实现这一点 - 它会更好地传达您的函数的意图:evalLatitude lat
和evalLongitude lng
都必须成功,否则您将失败错误信息。
import Control.Monad.Except
mkLatLngPoint :: LatitudeDMS -> LongitudeDMS -> Datum -> Except String LatLng
mkLatLngPoint lat lng dtm = do
lt <- withExcept (const "Invalid latitude") evalLatitude lat
ln <- withExcept (const "Invalid longitude") evalLongitude lng
let p = LatLngPoint { latitude = lt , longitude = ln, height = 0 }
pure (LatLng { point = p , datum = dtm })
where evalLatitude :: LatitudeDMS -> Except String Double
evalLatitude (North p) = dmsToLatLngPoint p 1
evalLatitude (South p) = dmsToLatLngPoint p (-1)
evalLongitude :: LongitudeDMS -> Except String Double
evalLongitude (East p) = dmsToLatLngPoint p 1
evalLongitude (West p) = dmsToLatLngPoint p (-1)
dmsToLatLngPoint :: DMSPoint -> Double -> Except String Double
dmsToLatLngPoint DMSPoint { degrees = d, minutes = m, seconds = s } cardinal
| d + m + s < 90 = throwError "Invalid point"
| otherwise = pure (cardinal * (d + m + s / 324.9))
请注意,此解决方案和case
解决方案的评估都不是他们需要的:只要其中一个失败,该函数就会整体失败(对于您的情况,请记住Haskell很懒!)
答案 1 :(得分:0)
我看到已经有一个已接受的答案,但只是提供了另一种解决方案(尽管非常相似)。按照此处列出的指导原则:https://www.fpcomplete.com/blog/2016/11/exceptions-best-practices-haskell(两种方式都可以阅读),你会得到类似的东西。
import Control.Monad.Catch
data LatitudeException = LatitudeException
instance Show LatitudeException where
show LatitudeException = "Invalid Latitude"
instance Exception LatitudeException
data LongitudeException = LongitudeException
instance Show LongitudeException where
show LongitudeException = "Invalid Longitude"
instance Exception LongitudeException
mkLatLngPoint :: (MonadThrow m) => LatitudeDMS -> LongitudeDMS -> Datum -> m LatLng
mkLatLngPoint lat lng dtm = do
lt <- evalLatitude lat
ln <- evalLongitude lng
let p = LatLngPoint { latitude = lt , longitude = ln, height = 0 }
return $ LatLng { point = p , datum = dtm }
where evalLatitude :: (MonadThrow m) => LatitudeDMS -> m Double
evalLatitude (North p) = case dmsToLatLngPoint p 1 of
(Just d) -> return d
Nothing -> throwM LatitudeException
evalLatitude (South p) = case dmsToLatLngPoint p (-1) of
(Just d) -> return d
Nothing -> throwM LatitudeException
evalLongitude :: (MonadThrow m) => LongitudeDMS -> m Double
evalLongitude (East p) = case dmsToLatLngPoint p 1 of
(Just d) -> return d
Nothing -> throwM LongitudeException
evalLongitude (West p) = case dmsToLatLngPoint p (-1) of
(Just d) -> return d
Nothing -> throwM LongitudeException
dmsToLatLngPoint :: DMSPoint -> Double -> Maybe Double
dmsToLatLngPoint DMSPoint { degrees = d, minutes = m, seconds = s } cardinal
| d + m + s < 90 = Nothing
| otherwise = Just (cardinal * (d + m + s / 324.9))
肯定会有更多的样板来处理,但会提供更多的灵活性。查看文章,看看您的情况是否有益处。