从值级别的依赖 type/using 类型级别绑定中提取类型级别值
Pull type-level value out of dependent type/using type-level bindings at value-level
当我在 Haskell 中有依赖类型时,如何在函数中使用存储在该类型中的值?我想编写的示例 Haskell 程序( 无法编译 ,因为 min
和 max
类型级别绑定不会扩展到值级别):
{-# LANGUAGE GADTs #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE DataKinds #-}
module CharRange (CharRange, ofChar, asChar) where
data CharRange :: Char -> Char -> * where
C :: Char -> CharRange min max
ofChar :: Char -> CharRange min max
ofChar c =
if min <= c && c <= max
then C c
else C min
asChar :: CharRange min max -> Char
asChar (C c) =
if min <= c && c <= max
then c
else min
我可以在 Idris 中做到这一点:
module CharRange
%default total
%access export
data CharRange : Char -> Char -> Type where
C : Char -> CharRange min max
ofChar : Char -> CharRange min max
ofChar c =
if min <= c && c <= max
then C c
else C min
asChar : CharRange min max -> Char
asChar (C c) =
if min <= c && c <= max
then c
else min
按预期编译和工作:
λΠ> the (CharRange 'a' 'z') $ ofChar 'M'
C 'a' : CharRange 'a' 'z'
λΠ> the (CharRange 'a' 'z') $ ofChar 'm'
C 'm' : CharRange 'a' 'z'
如何在不减少类型信息量的情况下将这个 Idris 程序翻译成 Haskell?
一种可能性(但我不相信这值得麻烦)是用 Nat
ural numbers 而不是他们编码的 Char
索引你的 CharRange
。
这样您就可以使用 GHC.TypeNats
获得这些类型级别界限的副本。
解决方案是这样的:
{-# LANGUAGE GADTs #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE ScopedTypeVariables #-}
module Ranged (CharRange, ofChar, asChar) where
import GHC.TypeNats
import Data.Char
import Data.Proxy
data CharRange :: Nat -> Nat -> * where
C :: Char -> CharRange min max
ofChar :: forall min max. (KnownNat min, KnownNat max)
=> Char -> CharRange min max
ofChar c =
let min = fromIntegral $ natVal (Proxy :: Proxy min) in
let max = fromIntegral $ natVal (Proxy :: Proxy max) in
if min <= fromEnum c && fromEnum c <= max
then C c
else C (toEnum min)
asChar :: forall min max. (KnownNat min, KnownNat max)
=> CharRange min max -> Char
asChar (C c) =
let min = fromIntegral $ natVal (Proxy :: Proxy min) in
let max = fromIntegral $ natVal (Proxy :: Proxy max) in
if min <= fromEnum c && fromEnum c <= max
then c
else toEnum min
如果您不想对边界做任何花哨的运算,而是将它们用作可配置常量,在整个计算过程中强制执行不变量,您还可以使用反射包。
好处是您的常量不必在编译时就知道。查看 reflection package 的文档以了解更多信息。例如,有一个实例 KnownNat n => Reifies (n :: Nat) Integer
所以存在一些互操作性(至少在一个方向上),例如使用 reifyNat
而不是 reify
来配置您的条款。
{-# LANGUAGE ScopedTypeVariables, TypeApplications, FlexibleContexts -#}
{-# LANGUAGE AllowAmbiguousTypes #-}
import Data.Reflection
import Data.Char
import Data.Proxy
newtype CharRange min max = C Char
--| this is the only thing that needs AllowAmbiguousTypes, but we don't need to write
-- Proxy everywhere. Choose your poison.
reflect' :: forall s a. Reifies s a => a
reflect' = reflect @s Proxy
ofChar :: forall min max. (Reifies min Char, Reifies max Char)
=> Char -> CharRange min max
ofChar c =
if reflect' @min <= c && c <= reflect' @max
then C c
else C $ reflect' @min
asChar :: forall min max. (Reifies min Char, Reifies max Char)
=> CharRange min max -> Char
asChar (C c) =
if reflect' @min <= c && c <= reflect' @max
then c
else reflect' @min
-- At the toplevel, you need to configure your 'constants'.
-- using reify you get Proxy parameters, that allows you to capture
-- the type variable that is used to reflect the arguments back inside
-- your computation.
-- this might seem like a lot of code, but since it's toplevel needs only
-- be done once.
main = print $ show $ reify 'a' $
\(_ :: Proxy min) -> reify 'z' $
\(_ :: Proxy max) -> asChar (ofChar @min @max '_')
当我在 Haskell 中有依赖类型时,如何在函数中使用存储在该类型中的值?我想编写的示例 Haskell 程序( 无法编译 ,因为 min
和 max
类型级别绑定不会扩展到值级别):
{-# LANGUAGE GADTs #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE DataKinds #-}
module CharRange (CharRange, ofChar, asChar) where
data CharRange :: Char -> Char -> * where
C :: Char -> CharRange min max
ofChar :: Char -> CharRange min max
ofChar c =
if min <= c && c <= max
then C c
else C min
asChar :: CharRange min max -> Char
asChar (C c) =
if min <= c && c <= max
then c
else min
我可以在 Idris 中做到这一点:
module CharRange
%default total
%access export
data CharRange : Char -> Char -> Type where
C : Char -> CharRange min max
ofChar : Char -> CharRange min max
ofChar c =
if min <= c && c <= max
then C c
else C min
asChar : CharRange min max -> Char
asChar (C c) =
if min <= c && c <= max
then c
else min
按预期编译和工作:
λΠ> the (CharRange 'a' 'z') $ ofChar 'M'
C 'a' : CharRange 'a' 'z'
λΠ> the (CharRange 'a' 'z') $ ofChar 'm'
C 'm' : CharRange 'a' 'z'
如何在不减少类型信息量的情况下将这个 Idris 程序翻译成 Haskell?
一种可能性(但我不相信这值得麻烦)是用 Nat
ural numbers 而不是他们编码的 Char
索引你的 CharRange
。
这样您就可以使用 GHC.TypeNats
获得这些类型级别界限的副本。
解决方案是这样的:
{-# LANGUAGE GADTs #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE ScopedTypeVariables #-}
module Ranged (CharRange, ofChar, asChar) where
import GHC.TypeNats
import Data.Char
import Data.Proxy
data CharRange :: Nat -> Nat -> * where
C :: Char -> CharRange min max
ofChar :: forall min max. (KnownNat min, KnownNat max)
=> Char -> CharRange min max
ofChar c =
let min = fromIntegral $ natVal (Proxy :: Proxy min) in
let max = fromIntegral $ natVal (Proxy :: Proxy max) in
if min <= fromEnum c && fromEnum c <= max
then C c
else C (toEnum min)
asChar :: forall min max. (KnownNat min, KnownNat max)
=> CharRange min max -> Char
asChar (C c) =
let min = fromIntegral $ natVal (Proxy :: Proxy min) in
let max = fromIntegral $ natVal (Proxy :: Proxy max) in
if min <= fromEnum c && fromEnum c <= max
then c
else toEnum min
如果您不想对边界做任何花哨的运算,而是将它们用作可配置常量,在整个计算过程中强制执行不变量,您还可以使用反射包。
好处是您的常量不必在编译时就知道。查看 reflection package 的文档以了解更多信息。例如,有一个实例 KnownNat n => Reifies (n :: Nat) Integer
所以存在一些互操作性(至少在一个方向上),例如使用 reifyNat
而不是 reify
来配置您的条款。
{-# LANGUAGE ScopedTypeVariables, TypeApplications, FlexibleContexts -#}
{-# LANGUAGE AllowAmbiguousTypes #-}
import Data.Reflection
import Data.Char
import Data.Proxy
newtype CharRange min max = C Char
--| this is the only thing that needs AllowAmbiguousTypes, but we don't need to write
-- Proxy everywhere. Choose your poison.
reflect' :: forall s a. Reifies s a => a
reflect' = reflect @s Proxy
ofChar :: forall min max. (Reifies min Char, Reifies max Char)
=> Char -> CharRange min max
ofChar c =
if reflect' @min <= c && c <= reflect' @max
then C c
else C $ reflect' @min
asChar :: forall min max. (Reifies min Char, Reifies max Char)
=> CharRange min max -> Char
asChar (C c) =
if reflect' @min <= c && c <= reflect' @max
then c
else reflect' @min
-- At the toplevel, you need to configure your 'constants'.
-- using reify you get Proxy parameters, that allows you to capture
-- the type variable that is used to reflect the arguments back inside
-- your computation.
-- this might seem like a lot of code, but since it's toplevel needs only
-- be done once.
main = print $ show $ reify 'a' $
\(_ :: Proxy min) -> reify 'z' $
\(_ :: Proxy max) -> asChar (ofChar @min @max '_')