首页 文章

Haskell得到代数参数的类型

提问于
浏览
3

我有一个类型

class IntegerAsType a where
  value :: a -> Integer

data T5
instance IntegerAsType T5 where value _ = 5

newtype (IntegerAsType q) => Zq q = Zq Integer deriving (Eq)

newtype (Num a, IntegerAsType n) => PolyRing a n = PolyRing [a]

我正试图为PolyRing类型制作一个漂亮的"show" . 特别是,我希望"show"打印出类型'a' . 是否有一个返回代数参数类型的函数(类型为'show')?

我试图做的另一种方式是使用模式匹配,但我遇到了内置类型和代数类型的问题 .

我希望每个Integer,Int和Zq q都有不同的结果 . (玩具示例:)

test :: (Num a, IntegerAsType q) => a -> a
(Int x) = x+1
(Integer x) = x+2
(Zq x) = x+3

这里至少有两个不同的问题 .

1)Int和Integer不是'Int'和'Integer'类型的数据构造函数 . 是否有这些类型的数据构造函数/如何与它们模式匹配?

2)虽然我的代码中没有显示,但Zq是Num的一个实例 . 我得到的问题是:

Ambiguous constraint `IntegerAsType q'
At least one of the forall'd type variables mentioned by the constraint 
must be reachable from the type after the '=>'
In the type signature for `test':
test :: (Num a, IntegerAsType q) => a -> a

我有点理解它为什么抱怨,但我不知道怎么解决这个问题 .

谢谢

编辑:我正在尝试使用测试功能的一个更好的例子:

test :: (Num a) => a -> a
test (Integer x) = x+2
test (Int x) = x+1
test (Zq x) = x

即使我们忽略了这样一个事实:我无法以这种方式构造整数和Ints(仍然想知道如何!)这个'test'不能编译,因为:

Could not deduce (a ~ Zq t0) from the context (Num a)

我对此函数的下一次尝试是使用类型签名:

test :: (Num a, IntegerAsType q) => a -> a

这导致了新的错误

Ambiguous constraint `IntegerAsType q'
At least one of the forall'd type variables mentioned by the constraint 
must be reachable from the type after the '=>'

我希望这让我的问题更加清晰......

2 回答

  • 4

    我'm not sure what you'正在使用 test 函数开车,但如果您愿意,可以执行以下操作:

    {-# LANGUAGE ScopedTypeVariables #-}
    class NamedType a where
        name :: a -> String
    instance NamedType Int where
        name _ = "Int"
    instance NamedType Integer where
        name _ = "Integer"
    instance NamedType q => NamedType (Zq q) where
        name _ = "Zq (" ++ name (undefined :: q) ++ ")"
    

    如果我没有跟进这个答案并发出警告,我不会做我的Stack Overflow任务:你要求的是非常非常奇怪的 . 你可能正在以一种非常单一的方式做某事,并将全力以赴地对抗语言 . 我强烈建议您的下一个问题是一个更广泛的设计问题,以便我们可以帮助您指导更具惯用性的解决方案 .

    Edit

    还有另外一半你的问题,即如何在输入上写一个 test 函数"pattern matches"来检查它是 IntIntegerZq 类型等等 . 你提供这个暗示性的代码片段:

    test :: (Num a) => a -> a
    test (Integer x) = x+2
    test (Int x) = x+1
    test (Zq x) = x
    

    这里有几件事需要澄清 .

    Haskell有三个级别的对象:值级别,类型级别和种类级别 . 值级别的一些示例包括 "Hello, world!"42 ,函数 \a -> afix (\xs -> 0:1:zipWith (+) xs (tail xs)) . 类型级别的一些示例包括 BoolIntMaybeMaybe IntMonad m => m () . 类似事物的一些例子包括 *(* -> *) -> * .

    水平是有序的;值级别对象按类型级别对象进行分类,类型级别对象按类型级别对象进行分类 . 我们使用 :: 编写分类关系,例如 32 :: Int"Hello, world!" :: [Char] . (对于此讨论,类型级别并不太有趣,但 * 对类型进行分类,箭头类型对类型构造函数进行分类 . 例如, Int :: *[Int] :: * ,但 [] :: * -> * . )

    现在,Haskell最基本的属性之一是每个级别都是完全隔离的 . 你永远不会在类型中看到类似 "Hello, world!" 的字符串;类似地,值级对象不会传递或操作类型 . 此外,值和类型有单独的命名空间 . 以 Maybe 为例:

    data Maybe a = Nothing | Just a
    

    此声明在类型级别创建新名称 Maybe :: * -> * ,在值级别创建两个新名称 Nothing :: Maybe aJust :: a -> Maybe a . 一种常见的模式是对类型构造函数和它的值构造函数使用相同的名称,如果只有一个;例如,您可能会看到

    newtype Wrapped a = Wrapped a
    

    它在类型级别声明一个新名称 Wrapped :: * -> * ,同时在值级别声明一个不同的名称 Wrapped :: a -> Wrapped a . 一些特别常见(和令人困惑的例子)包括 () ,它既是值级对象(类型为 () )又是类型级对象(类型为 * ),而 [] ,它们都是值级对象(类型为对象) [a] )和类型级对象(种类 * -> * ) . 请注意,值源级别和类型级别对象碰巧在源代码中拼写相同这一事实只是巧合!如果你想让你的读者感到困惑,你可以写得很好

    newtype Huey  a = Louie a
    newtype Louie a = Dewey a
    newtype Dewey a = Huey  a
    

    这三个声明中没有一个完全相互关联!

    现在,我们最终可以解决 test 上面出现的问题: IntegerInt 不是值构造函数,因此它们可以在类型定义中放置类型名称!到现在为止,你可能希望你写了 test' 代替:

    test' :: Num a => a -> a
    test' (x :: Integer) = x + 2
    test' (x :: Int) = x + 1
    test' (Zq x :: Zq a) = x
    

    ......但是,唉,它不允许依赖类型级别的东西 . 你可以做的是在每个 IntIntegerZq a 类型中编写单独的函数:

    testInteger :: Integer -> Integer
    testInteger x = x + 2
    
    testInt :: Int -> Int
    testInt x = x + 1
    
    testZq :: Num a => Zq a -> Zq a
    testZq (Zq x) = Zq x
    

    然后,当我们想要进行测试时,我们可以调用这些函数中的相应函数 . 由于我们使用的是静态类型的语言,因此这些函数中的任何一个都适用于任何特定的语言变量 .

    现在,记住调用正确的函数有点繁琐,因此Haskell提供了一些方便:您可以让编译器在编译时为您选择这些函数之一 . 这种机制是课程背后的重要思想 . 它看起来像这样:

    class Testable a where test :: a -> a
    instance Testable Integer where test = testInteger
    instance Testable Int where test = testInt
    instance Num a => Testable (Zq a) where test = testZq
    

    现在,看起来有一个名为 test 的函数可以处理 IntInteger 或数字 Zq 's -- but in fact there are three functions, and the compiler is transparently choosing one for you. And that'中的任何一个重要的洞察力 . test 的类型:

    test :: Testable a => a -> a
    

    ...看起来第一个脸红,就像它是一个可以是任何 Testable 类型的值的函数 . 但事实上,它是一个可以专门用于任何 Testable 类型的函数 - 然后只接受该类型的值!这种差异解释了原始 test 函数没有多个模式与不同类型的变量的另一个原因,因为该函数一次只能在单个类型上工作 .

    上面的类 NamedTypeTestable 背后的想法可以概括一点;如果你这样做,你会得到上面的hammar建议的 Typeable 课程 .

    我觉得现在我已经絮絮叨叨了,可能比我澄清的事情更困惑,但是给我留言说哪些部分不清楚,我会尽我所能 .

  • 2

    是否有一个返回代数参数类型的函数(类型的'show')?

    我认为Data.Typeable可能就是你要找的东西 .

    Prelude> :m + Data.Typeable
    Prelude Data.Typeable> typeOf (1 :: Int)
    Int
    Prelude Data.Typeable> typeOf (1 :: Integer)
    Integer
    

    请注意,这不适用于任何类型,只有具有 Typeable 实例的类型 . 使用扩展名 DeriveDataTypeable ,您可以让编译器自动为您自己的类型派生这些:

    {-# LANGUAGE DeriveDataTypeable #-}
    
    import Data.Typeable
    
    data Foo = Bar
      deriving Typeable
    
    *Main> typeOf Bar
    Main.Foo
    

    在你的问题的后半部分,我没有完全得到你想要做的事情,但希望这应该有所帮助 .

相关问题