声明实例的类型推断

时间:2013-08-13 18:31:20

标签: haskell types

我想出了一个很好的练习,但是无法使它发挥作用。

我的想法是尝试表达罗马数字,使得类型检查器会告诉我数字是否有效。

    {-# LANGUAGE RankNTypes
               , MultiParamTypeClasses #-}

    data One a b c = One a deriving (Show, Eq)
    data Two a b c = Two (One a b c) (One a b c) deriving (Show, Eq)
    data Three a b c = Three (One a b c) (Two a b c) deriving (Show, Eq)
    data Four a b c = Four (One a b c) (Five a b c) deriving (Show, Eq)
    data Five a b c = Five b deriving (Show, Eq)
    data Six a b c = Six (Five a b c) (One a b c) deriving (Show, Eq)
    data Seven a b c = Seven (Five a b c) (Two a b c) deriving (Show, Eq)
    data Eight a b c = Eight (Five a b c) (Three a b c) deriving (Show, Eq)
    data Nine a b c d e = Nine (One a b c) (One c d e) deriving (Show, Eq)

    data Z = Z deriving (Show, Eq) -- dummy for the last level
    data I = I deriving (Show, Eq)
    data V = V deriving (Show, Eq)
    data X = X deriving (Show, Eq)
    data L = L deriving (Show, Eq)
    data C = C deriving (Show, Eq)
    data D = D deriving (Show, Eq)
    data M = M deriving (Show, Eq)

    i :: One I V X
    i = One I

    v :: Five I V X
    v = Five V

    x :: One X L C
    x = One X

    l :: Five X L C
    l = Five L

    c :: One C D M
    c = One C

    d :: Five C D M
    d = Five D

    m :: One M Z Z
    m = One M

    infixr 4 #

    class RomanJoiner a b c where
      (#) :: a -> b -> c

    instance RomanJoiner (One a b c) (One a b c) (Two a b c) where
      (#) = Two

    instance RomanJoiner (One a b c) (Two a b c) (Three a b c) where
      (#) = Three

    instance RomanJoiner (One a b c) (Five a b c) (Four a b c) where
      (#) = Four

    instance RomanJoiner (Five a b c) (One a b c) (Six a b c) where
      (#) = Six

    instance RomanJoiner (Five a b c) (Two a b c) (Seven a b c) where
      (#) = Seven

    instance RomanJoiner (Five a b c) (Three a b c) (Eight a b c) where
      (#) = Eight

    instance RomanJoiner (One a b c) (One c d e) (Nine a b c d e) where
      (#) = Nine

    main = print $ v # i # i

这可能会有所不同,并且解决方案是不完整的,但是现在我需要理解为什么它抱怨没有RomanJoiner(One IVX)(One IVX)b0的实例,而我认为我声明了这样的木匠。

1 个答案:

答案 0 :(得分:4)

问题是没有根据实例选择实例 只有一个有效:一个扩展FunctionalDependencies有助于获得更多 类型推断。启用它,并用| a b -> c说明类型 可以从a # ba的类型推断出b。不幸的是,这不是您必须做的唯一事情,因为您会收到错误Functional dependencies conflict between instance declarations。使用HList中定义的一些类(这些可以在其他任何地方定义),冲突的两个实例可以合并为一个,其中两个(如果计算错误则为3)可能的结果是根据某些类型是否相等来选择的

关于这个解决方案的一些评论是丑陋的:

  1. 您不必复制值类型级别的内容 再次级别(hCond vs HCond)如果你有更懒的Show实例(比如 instance Show I where show _ = "I")。
  2. 更多现代扩展TypeFamilies许多中间类型 变量ba, bb, bc, babc ...可以消除。

    {-# LANGUAGE RankNTypes, MultiParamTypeClasses, FunctionalDependencies, ScopedTypeVariables, UndecidableInstances, FlexibleContexts, FlexibleInstances #-}
    import Data.HList hiding ((#))
    import Data.HList.TypeEqGeneric1
    import Data.HList.TypeCastGeneric1
    import Unsafe.Coerce
    
    data One a b c = One a deriving (Show, Eq)
    data Two a b c = Two (One a b c) (One a b c) deriving (Show, Eq)
    data Three a b c = Three (One a b c) (Two a b c) deriving (Show, Eq)
    data Four a b c = Four (One a b c) (Five a b c) deriving (Show, Eq)
    data Five a b c = Five b deriving (Show, Eq)
    data Six a b c = Six (Five a b c) (One a b c) deriving (Show, Eq)
    data Seven a b c = Seven (Five a b c) (Two a b c) deriving (Show, Eq)
    data Eight a b c = Eight (Five a b c) (Three a b c) deriving (Show, Eq)
    data Nine a b c d e = Nine (One a b c) (One c d e) deriving (Show, Eq)
    
    data Z = Z deriving (Show, Eq) -- dummy for the last level
    data I = I deriving (Show, Eq)
    data V = V deriving (Show, Eq)
    data X = X deriving (Show, Eq)
    data L = L deriving (Show, Eq)
    data C = C deriving (Show, Eq)
    data D = D deriving (Show, Eq)
    data M = M deriving (Show, Eq)
    
    i :: One I V X
    i = One I
    
    v :: Five I V X
    v = Five V
    
    x :: One X L C
    x = One X
    
    l :: Five X L C
    l = Five L
    
    c :: One C D M
    c = One C
    
    d :: Five C D M
    d = Five D
    
    m :: One M Z Z
    m = One M
    
    infixr 4 #
    
    class RomanJoiner a b c | a b -> c where
        (#) :: a -> b -> c
    
    
    instance RomanJoiner (One a b c) (Two a b c) (Three a b c) where
        (#) = Three
    
    instance RomanJoiner (One a b c) (Five a b c) (Four a b c) where
        (#) = Four
    
    instance RomanJoiner (Five a b c) (One a b c) (Six a b c) where
        (#) = Six
    
    instance RomanJoiner (Five a b c) (Two a b c) (Seven a b c) where
        (#) = Seven
    
    instance RomanJoiner (Five a b c) (Three a b c) (Eight a b c) where
        (#) = Eight
    
    data Error = Error
    instance forall a b c a' b' c' ba bb bc bab babc z bn nine.
       (TypeEq a a' ba,
        TypeEq b b' bb,
        TypeEq c c' bc,
        HAnd ba bb bab,
        HAnd bab bc babc,
    
        TypeEq c a' bn,
        HCond bn (Nine a b c b' c') Error nine,
    
        HCond babc (Two a b c) nine  z) =>
            RomanJoiner (One a b c) (One a' b' c') z where
        (#) x y = hCond (undefined :: babc)
                    (Two (uc x :: One a b c) (uc y :: One a b c)) $
                  hCond (undefined :: bn)
                    (Nine (uc x :: One a b c) (uc y :: One c b' c'))
                    Error
            where uc = unsafeCoerce
    
    main = print $ v # i # i
    {-
    Prints with ghc 762, HList-0.2.3
    
    *Main> main
    Seven (Five V) (Two (One I) (One I)
    
    -}