在Haskell中实现非位置关键字参数

时间:2017-07-27 21:10:05

标签: haskell types type-theory

我正在尝试在Haskell中实现关键字参数,类似于Ocaml中的参数。我的目标是拥有可以按任何顺序传递的参数,并且可以部分地应用于函数调用中(产生一个带有剩余关键字参数的新函数)。

我尝试使用DataKinds和一个类型类来实现这一点,类型类表示可以转换为函数a -> b的值。"我们的想法是,一个带有两个关键字参数foobar的函数可以转换为看似foo -> bar -> x的函数或看起来像bar -> foo -> x的函数。只要foobar具有不同类型(并且x本身不是foobar的函数),这应该是明确的,这是我用Kwarg GADT试图实现的目标。

Fun类中的功能依赖性旨在使fab之间的关系更明确,但我不确定这是否真的有帮助。无论是否有编译器错误。

我启用了以下语言扩展程序:

{-# LANGUAGE DataKinds #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE UndecidableInstances #-}

Quoter.hs的内容:

module Quoter where

import GHC.TypeLits (Symbol)

data Kwarg :: Symbol -> * -> * where
  Value :: a -> Kwarg b a

class Fun f a b | f a -> b where
  runFun :: f -> a -> b

instance (Fun f' a' b) => Fun (a -> f') a' (a -> b) where
  runFun f a' = \a -> runFun (f a) a'

instance Fun (Kwarg name t -> b) (Kwarg name t) b where
  runFun = id

Main.hs的内容:

module Main where

import Quoter

test :: (Num a) => Kwarg "test" a -> Kwarg "some" a -> a
test (Value i) (Value j) = i + j

main = putStrLn $ show $
  runFun test (Value 5 :: Kwarg "some" Int) (Value 6 :: Kwarg "test" Int)

这是使用ghc构建时出现的错误:

Main.hs:18:3: error:
    • Couldn't match type ‘Kwarg "some" Int -> b’ with ‘Int’
        arising from a functional dependency between:
          constraint ‘Fun (Kwarg "some" Int -> Int) (Kwarg "some" Int) Int’
            arising from a use of ‘runFun’
          instance ‘Fun (a -> f') a' (a -> b1)’ at <no location info>
    • In the second argument of ‘($)’, namely
        ‘runFun
           test (Value 5 :: Kwarg "some" Int) (Value 6 :: Kwarg "test" Int)’
      In the second argument of ‘($)’, namely
        ‘show
         $ runFun
             test (Value 5 :: Kwarg "some" Int) (Value 6 :: Kwarg "test" Int)’
      In the expression:
        putStrLn
        $ show
          $ runFun
              test (Value 5 :: Kwarg "some" Int) (Value 6 :: Kwarg "test" Int)

你知道我需要改变什么才能编译吗?这种一般方法是否合理,为了使其发挥作用,我需要更好地理解什么?

谢谢!

ghc --version的输出:

The Glorious Glasgow Haskell Compilation System, version 8.0.2

1 个答案:

答案 0 :(得分:1)

问题是您的实例重叠:

instance (Fun f' a' b) => Fun (a -> f') a' (a -> b) where

instance Fun (Kwarg name t -> b) (Kwarg name t) b
  -- this is actually a special case of the above, with a ~ a' ~ KWarg name t

如果我们用等效的相关类型族来替换函数依赖(IMO通常有点难以推理),那就更清楚了:

{-# LANGUAGE TypeFamilies #-}

class Fun f a where
  type FRes f a :: *
  runFun :: f -> a -> FRes f a

instance Fun f' a' => Fun (a -> f') a' where
  type FRes (a -> f') a' = a -> FRes f' a'
  runFun f a' = \a -> runFun (f a) a'

instance Fun (Kwarg name t -> b) (Kwarg name t) where
  type FRes (Kwarg name t -> b) (Kwarg name t) = b
  runFun = id

在这种情况下,编译器消息非常清楚:

    Conflicting family instance declarations:
      FRes (a -> f') a' = a -> FRes f' a'
        -- Defined at /tmp/wtmpf-file10498.hs:20:8
      FRes (Kwarg name t -> b) (Kwarg name t) = b
        -- Defined at /tmp/wtmpf-file10498.hs:24:8
   |
20 |   type FRes (a -> f') a' = a -> FRes f' a'
   |        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

重叠实例是一个大问题,特别是在需要解决非常重要的类型级函数时。但是,这不是以前从未遇到过的问题。解决方案是从基于实例子句的类型级函数(即FunDeps或相关类型族)切换到closed type families

type family FRes f a where
  FRes (Kwarg name t -> b) (Kwarg name t) = b
  FRes (a -> f') a' = a -> FRes f' a'

要实际实现runFun,您仍然需要一个具有重叠实例的类,但是现在这些类可以被黑客攻击以与GHC编译指示一起使用:

class Fun f a where
  runFun :: f -> a -> FRes f a

instance {-# OVERLAPPABLE #-} (Fun f' a', FRes (a -> f') a' ~ (a->FRes f' a'))
               => Fun (a -> f') a' where
  runFun f a' = \a -> runFun (f a) a'

instance {-# OVERLAPS #-} Fun (Kwarg name t -> b) (Kwarg name t) where
  runFun = id

因此,您的测试现在可以正常运行。

不幸的是,会做它实际上应该做的事情:允许更改参数顺序。

main = putStrLn $ show
  ( runFun test (Value 5 :: Kwarg "some" Int) (Value 6 :: Kwarg "test" Int)
  , runFun test (Value 5 :: Kwarg "test" Int) (Value 6 :: Kwarg "some" Int) )

给出

/tmp/wtmpf-file10498.hs:34:5: error:
    • Couldn't match expected type ‘Kwarg "some" Int -> b0’
                  with actual type ‘FRes
                                      (Kwarg "test" a0 -> Kwarg "some" a0 -> a0) (Kwarg "test" Int)’
      The type variables ‘a0’, ‘b0’ are ambiguous
    • The function ‘runFun’ is applied to three arguments,
      but its type ‘(Kwarg "test" a0 -> Kwarg "some" a0 -> a0)
                    -> Kwarg "test" Int
                    -> FRes
                         (Kwarg "test" a0 -> Kwarg "some" a0 -> a0) (Kwarg "test" Int)’
      has only two
      In the expression:
        runFun
          test (Value 5 :: Kwarg "test" Int) (Value 6 :: Kwarg "some" Int)
      In the first argument of ‘show’, namely
        ‘(runFun
            test (Value 5 :: Kwarg "some" Int) (Value 6 :: Kwarg "test" Int), 
          runFun
            test (Value 5 :: Kwarg "test" Int) (Value 6 :: Kwarg "some" Int))’
   |
34 |   , runFun test (Value 5 :: Kwarg "test" Int) (Value 6 :: Kwarg "some" Int) )
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

基本问题是类型推断的方向不太有利:从起始值到最终结果。这是大多数语言中唯一可用的方向,但在Haskell中,从最终结果推断到单个表达式几乎总是更好,因为只有外部结果始终可用于与环境的统一。最后,这会导致你像

type family FFun a b where

即。函数的类型取决于您想要的结果和您已经可以提供的参数。根据手头的参数是否恰好是函数所期望的第一个,您将不会有重叠的实例;相反,你会构建某种类型级别的映射,包括所有键控参数,并让函数接受一个这样的映射(或者,等价地,所有参数按字母顺序)。