mirror of
https://github.com/facebook/duckling.git
synced 2024-12-01 16:26:20 +03:00
192 lines
6.6 KiB
Haskell
192 lines
6.6 KiB
Haskell
|
-- Copyright (c) 2016-present, Facebook, Inc.
|
||
|
-- All rights reserved.
|
||
|
--
|
||
|
-- This source code is licensed under the BSD-style license found in the
|
||
|
-- LICENSE file in the root directory of this source tree. An additional grant
|
||
|
-- of patent rights can be found in the PATENTS file in the same directory.
|
||
|
|
||
|
|
||
|
-- | This module shadows names of some units previously defined in
|
||
|
-- 'Duckling.Distance.Types'.
|
||
|
-- Therefore, the units from that module must be used qualified by module.
|
||
|
module Duckling.DistanceUnits.Types
|
||
|
( ContextualDistance (..)
|
||
|
, toSystemUnit
|
||
|
, toRawUnit
|
||
|
) where
|
||
|
|
||
|
import Data.Semigroup
|
||
|
import Data.Tuple.Extra (both)
|
||
|
import Prelude
|
||
|
|
||
|
import qualified Duckling.Distance.Types as TDist
|
||
|
|
||
|
|
||
|
-- | Supports deferred resolution of ambiguous units.
|
||
|
-- Note that this sum type cannot be simply replaced by
|
||
|
-- "Maybe (ContextualDistance Double DeferrableUnit)"
|
||
|
-- (with "Nothing" representing "Nonrelatable").
|
||
|
-- See "NOTE A" below.
|
||
|
data ContextualDistance
|
||
|
= Nonrelatable
|
||
|
-- ^ If two different ambiguous units were to be composed, then
|
||
|
-- it would not be possible to decide which to resolve to. As of today,
|
||
|
-- this won't happen since for now "M" is the lone ambiguous unit.
|
||
|
| ContextualDistance Double DeferrableUnit
|
||
|
|
||
|
-- | This represents Units that have not yet been established as unrelatable.
|
||
|
-- The "Definite" constructor is purposely listed in front of "Ambiguous",
|
||
|
-- so that it will be preferred when taking choosing the "min" between
|
||
|
-- an "Ambiguous" and "Definite" unit.
|
||
|
data DeferrableUnit
|
||
|
= Definite SystemUnit
|
||
|
| Ambiguous AmbiguousUnit
|
||
|
deriving (Eq, Ord)
|
||
|
|
||
|
-- | These measurement-system-specific units exist to disambiguate
|
||
|
-- the \"M\" of the 'Unit' type and to maintain the measurement-system context
|
||
|
-- for resolving to the appropriate precision.
|
||
|
--
|
||
|
-- It's also just handy to have separate programmatically accessible lists
|
||
|
-- of units for each type of measurement system.
|
||
|
data SystemUnit
|
||
|
= Metric MetricUnit
|
||
|
| Imperial ImperialUnit
|
||
|
deriving (Eq, Ord)
|
||
|
|
||
|
data ImperialUnit
|
||
|
= Inch
|
||
|
| Foot
|
||
|
| Yard
|
||
|
| Mile
|
||
|
deriving (Eq, Ord)
|
||
|
|
||
|
data MetricUnit
|
||
|
= Millimetre
|
||
|
| Centimetre
|
||
|
| Metre
|
||
|
| Kilometre
|
||
|
deriving (Eq, Ord)
|
||
|
|
||
|
-- | Currently there is only one actually ambiguous unit, but this design allows
|
||
|
-- for expansion.
|
||
|
data AmbiguousUnit
|
||
|
= M -- ^ "Miles" or "Metres"
|
||
|
deriving (Eq, Ord)
|
||
|
|
||
|
-- | Represents a value with an unambiguous unit
|
||
|
data UnitValuePair = UnitValuePair SystemUnit Double
|
||
|
|
||
|
sumScaledUnits :: SystemUnit -> (UnitValuePair, UnitValuePair) -> Double
|
||
|
sumScaledUnits preferredUnit = uncurry (+) . both (scaleUnits preferredUnit)
|
||
|
|
||
|
-- | Determine the definite meaning of an "Ambiguous" unit using the context of
|
||
|
-- another "Definite" unit
|
||
|
reconcileAmbiguousWithDefinite ::
|
||
|
Double
|
||
|
-> AmbiguousUnit
|
||
|
-> Double
|
||
|
-> SystemUnit
|
||
|
-> ContextualDistance
|
||
|
reconcileAmbiguousWithDefinite av au dv du =
|
||
|
ContextualDistance combinedValue $ Definite preferredUnit
|
||
|
where
|
||
|
resolvedAmbiguousUnit = resolveUnit au du
|
||
|
preferredUnit = du `min` resolvedAmbiguousUnit
|
||
|
combinedValue = sumScaledUnits preferredUnit
|
||
|
(UnitValuePair resolvedAmbiguousUnit av, UnitValuePair du dv)
|
||
|
|
||
|
-- | When both Metric and Imperial units are given, resolve to Metric.
|
||
|
-- Otherwise, preserve the original measurement system and use the smaller unit.
|
||
|
-- For the purpose of this resolution, all Metric units are considered "smaller"
|
||
|
-- than Imperial units.
|
||
|
instance Semigroup ContextualDistance where
|
||
|
|
||
|
_ <> Nonrelatable = Nonrelatable
|
||
|
Nonrelatable <> _ = Nonrelatable
|
||
|
|
||
|
(ContextualDistance v1 u@(Ambiguous u1))
|
||
|
<> (ContextualDistance v2 (Ambiguous u2))
|
||
|
| u1 == u2 = ContextualDistance (v1 + v2) u
|
||
|
| otherwise = Nonrelatable
|
||
|
-- NOTE A: Needing to return "Nonrelatable" in this edge case is why
|
||
|
-- the two-member "ContextualDistance" sum type cannot be simply
|
||
|
-- represented as (Maybe (ContextualDistance Double DeferrableUnit)).
|
||
|
|
||
|
(ContextualDistance av (Ambiguous au))
|
||
|
<> (ContextualDistance dv (Definite du)) =
|
||
|
reconcileAmbiguousWithDefinite av au dv du
|
||
|
|
||
|
(ContextualDistance dv (Definite du))
|
||
|
<> (ContextualDistance av (Ambiguous au)) =
|
||
|
reconcileAmbiguousWithDefinite av au dv du
|
||
|
|
||
|
(ContextualDistance v1 (Definite u1))
|
||
|
<> (ContextualDistance v2 (Definite u2)) =
|
||
|
ContextualDistance v $ Definite u
|
||
|
where
|
||
|
v = sumScaledUnits u (UnitValuePair u1 v1, UnitValuePair u2 v2)
|
||
|
u = u1 `min` u2
|
||
|
|
||
|
resolveUnit :: AmbiguousUnit -> SystemUnit -> SystemUnit
|
||
|
resolveUnit M (Metric _) = Metric Metre
|
||
|
resolveUnit M (Imperial _) = Imperial Mile
|
||
|
|
||
|
|
||
|
-- | Disambiguation of original Unit type
|
||
|
toSystemUnit :: TDist.Unit -> DeferrableUnit
|
||
|
toSystemUnit TDist.M = Ambiguous M
|
||
|
toSystemUnit TDist.Millimetre = Definite $ Metric Millimetre
|
||
|
toSystemUnit TDist.Centimetre = Definite $ Metric Centimetre
|
||
|
toSystemUnit TDist.Metre = Definite $ Metric Metre
|
||
|
toSystemUnit TDist.Kilometre = Definite $ Metric Kilometre
|
||
|
toSystemUnit TDist.Inch = Definite $ Imperial Inch
|
||
|
toSystemUnit TDist.Foot = Definite $ Imperial Foot
|
||
|
toSystemUnit TDist.Yard = Definite $ Imperial Yard
|
||
|
toSystemUnit TDist.Mile = Definite $ Imperial Mile
|
||
|
|
||
|
-- | Reconversion to original Unit type
|
||
|
toRawUnit :: DeferrableUnit -> TDist.Unit
|
||
|
toRawUnit (Ambiguous M) = TDist.M
|
||
|
toRawUnit (Definite (Metric Millimetre)) = TDist.Millimetre
|
||
|
toRawUnit (Definite (Metric Centimetre)) = TDist.Centimetre
|
||
|
toRawUnit (Definite (Metric Metre)) = TDist.Metre
|
||
|
toRawUnit (Definite (Metric Kilometre)) = TDist.Kilometre
|
||
|
toRawUnit (Definite (Imperial Inch)) = TDist.Inch
|
||
|
toRawUnit (Definite (Imperial Foot)) = TDist.Foot
|
||
|
toRawUnit (Definite (Imperial Yard)) = TDist.Yard
|
||
|
toRawUnit (Definite (Imperial Mile)) = TDist.Mile
|
||
|
|
||
|
-- | Convert a distance to the given units.
|
||
|
-- This only works if the unit is unambiguous.
|
||
|
scaleUnits ::
|
||
|
SystemUnit -- ^ target unit
|
||
|
-> UnitValuePair -- ^ Original unit and value
|
||
|
-> Double
|
||
|
scaleUnits targetUnit (UnitValuePair startingUnit v)
|
||
|
| startingUnit == targetUnit = v
|
||
|
| otherwise = inSIUnits v startingUnit / inSIUnits 1 targetUnit
|
||
|
|
||
|
-- | This is used when distances
|
||
|
-- must be normalized across measurement systems.
|
||
|
-- The Metric metre is the Standard International unit of distance.
|
||
|
inSIUnits :: Double -> SystemUnit -> Double
|
||
|
inSIUnits val (Metric u) = inMetres u val
|
||
|
inSIUnits val (Imperial u) = inInches u val * metersPerInch
|
||
|
|
||
|
-- | This conversion factor is exact.
|
||
|
metersPerInch :: Double
|
||
|
metersPerInch = 0.0254
|
||
|
|
||
|
inMetres :: Fractional a => MetricUnit -> a -> a
|
||
|
inMetres Millimetre n = n / 1000
|
||
|
inMetres Centimetre n = n / 100
|
||
|
inMetres Metre n = n
|
||
|
inMetres Kilometre n = n * 1000
|
||
|
|
||
|
inInches :: Num a => ImperialUnit -> a -> a
|
||
|
inInches Inch n = n
|
||
|
inInches Foot n = 12 * n
|
||
|
inInches Yard n = 3 * inInches Foot n
|
||
|
inInches Mile n = 5280 * inInches Foot n
|