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

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

我试图使用DataKinds和一个类型类来实现它,该类表示“可以转换为函数a – > b的值”.这个想法是一个带有两个关键字参数foo和bar的函数可以转换成一个看起来像foo的函数 – >吧 – > x或看起来像bar的函数 – > foo – > X.这应该是明确的,只要foo和bar有不同的类型(并且x本身不是一个需要foo或bar的函数),这是我用Kwarg GADT试图实现的.

Fun类中的函数依赖性意味着使f,a和b之间的关系更加明确,但我不确定这是否真的有帮助.我有一个编译器错误,有或没有它.

我启用了以下语言扩展:

{-# 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,您仍然需要一个具有重叠实例的类,但是现在这些类可以被黑客攻击以与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

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

点赞