如何满足存在量化值的约束?

时间:2017-10-21 20:44:54

标签: haskell

为了学习如何使用haskell中的依赖数据类型,我遇到了以下问题:

假设你有一个如下函数:

mean :: ((1 GHC.TypeLits.<=? n) ~ 'True, GHC.TypeLits.KnownNat n) => R n -> ℝ

hmatrix库中定义,那么如何在具有存在类型的向量上使用它? E.g:

{-# LANGUAGE DataKinds           #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeOperators       #-}

import Data.Proxy                   (Proxy (..))
import GHC.TypeLits
import Numeric.LinearAlgebra.Static

getUserInput =
  let userInput = 3   -- pretend it's unknown at compile time
      seed      = 42
  in existentialCrisis seed userInput

existentialCrisis seed userInput
  | userInput <= 0 = 0
  | otherwise =
    case someNatVal userInput of
      Nothing -> undefined -- let's ignore this case for now
      Just (SomeNat (proxy :: Proxy n)) ->
        let someVector = randomVector seed Gaussian :: R n
        in mean someVector -- I know that 'n > 0' but the compiler doesn't

这会出现以下错误:

• Couldn't match type ‘1 <=? n’ with ‘'True’
    arising from a use of ‘mean’

确实有道理,但经过一些谷歌搜索和摆弄,我无法找到如何处理这个问题。如何根据用户输入获取n :: Nat,以使其满足1 <= n约束?我认为必须有可能,因为someNatVal函数已经成功地满足KnownNat约束,基于输入不是负的条件。

在我看来,在使用依赖类型时这是常见的事情,也许答案很明显,但我不会看到它。

所以我的问题:

一般来说,如何在范围内引入存在类型来满足某些函数所需的约束?

我的尝试:

  • 令我惊讶的是,即便进行以下修改

        let someVector = randomVector seed Gaussian :: R (n + 1)
    

    给出了类型错误:

    • Couldn't match type ‘1 <=? (n + 1)’ with ‘'True’
        arising from a use of ‘mean’
    

    此外,向<=?添加额外的实例以证明此等式不起作用,<=?已关闭。

  • 我尝试了将GADTs与类型类组合在一起的方法,如this answer to a previous question of mine中所述,但无法使其正常工作。

1 个答案:

答案 0 :(得分:1)

感谢@danidiaz指出我正确的方向,typelist-witnesses文档为我的问题提供了nearly direct answer。在Google上搜索解决方案时,我似乎使用了错误的搜索字词。

所以这是一个自包含的可编译解决方案:

{-# LANGUAGE DataKinds           #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeOperators       #-}
{-# LANGUAGE TypeFamilies        #-}

import Data.Proxy                   (Proxy (..))
import Data.Type.Equality           ((:~:)(Refl))
import GHC.TypeLits
import GHC.TypeLits.Compare
import Numeric.LinearAlgebra.Static

existentialCrisis :: Int -> Int -> IO (Double)
existentialCrisis seed userInput =
    case someNatVal (fromIntegral userInput) of
      Nothing -> print "someNatVal failed" >> return 0
      Just (SomeNat (proxy :: Proxy n)) ->
        case isLE (Proxy :: Proxy 1) proxy of
          Nothing -> print "isLE failed" >> return 0
          Just Refl ->
            let someVector = randomVector seed Gaussian :: R n
            in do
              print userInput
              -- I know that 'n > 0' and so does the compiler
              return (mean someVector)

它适用于仅在运行时已知的输入:

λ: :l ExistentialCrisis.hs 
λ: existentialCrisis 41 1
(0.2596687587224799 :: R 1)
0.2596687587224799
*Main
λ: existentialCrisis 41 0
"isLE failed"
0.0
*Main
λ: existentialCrisis 41 (-1)
"someNatVal failed"
0.0

typelist-witnesses看起来很多unsafeCoerce在幕后。但是界面是类型安全的,因此对于实际使用情况来说并不是那么重要。

修改

如果您对此问题感兴趣,也可能会发现这篇文章很有趣:https://stackoverflow.com/a/41615278/2496293