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

Implementing non-positional keyword arguments in Haskell

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

我尝试使用 DataKinds 和代表 "a value that can be converted into a function a -> b." 的类型 class 来实现这个想法是一个函数接受两个关键字参数,foobar,可以转换为类似于 foo -> bar -> x 的函数或类似于 bar -> foo -> x 的函数。这应该是明确的,只要 foobar 具有不同的类型(并且 x 本身不是一个采用 foobar 的函数),这是我试图通过 Kwarg GADT 实现的目标。

Funclass中的函数依赖是为了使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

问题是您的实例重叠:

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,您仍然需要一个具有重叠实例的 class,但是这些现在可以被破解以与 GHC pragmas 一起工作:

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

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