在 Haskell 中实现非位置关键字参数
Implementing non-positional keyword arguments in Haskell
我正在尝试在 Haskell 中实现关键字参数,类似于 Ocaml 中的关键字参数。我的目标是拥有可以按任何顺序传递的参数,并且可以在函数调用中部分应用(产生一个接受剩余关键字参数的新函数)。
我尝试使用 DataKinds 和代表 "a value that can be converted into a function a -> b
." 的类型 class 来实现这个想法是一个函数接受两个关键字参数,foo
和 bar
,可以转换为类似于 foo -> bar -> x
的函数或类似于 bar -> foo -> x
的函数。这应该是明确的,只要 foo
和 bar
具有不同的类型(并且 x
本身不是一个采用 foo
或 bar
的函数),这是我试图通过 Kwarg
GADT 实现的目标。
Fun
class中的函数依赖是为了使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
,您仍然需要一个具有重叠实例的 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
即函数的类型取决于您想要的结果和您已经可以提供的参数。然后,根据手头的参数是否恰好是函数期望的第一个参数,您将不会有重叠的实例;相反,您将构建某种类型级别的映射,包括所有键控参数,并让函数接受一个这样的映射(或者,等效地,按字母顺序排列的所有参数)。
我正在尝试在 Haskell 中实现关键字参数,类似于 Ocaml 中的关键字参数。我的目标是拥有可以按任何顺序传递的参数,并且可以在函数调用中部分应用(产生一个接受剩余关键字参数的新函数)。
我尝试使用 DataKinds 和代表 "a value that can be converted into a function a -> b
." 的类型 class 来实现这个想法是一个函数接受两个关键字参数,foo
和 bar
,可以转换为类似于 foo -> bar -> x
的函数或类似于 bar -> foo -> x
的函数。这应该是明确的,只要 foo
和 bar
具有不同的类型(并且 x
本身不是一个采用 foo
或 bar
的函数),这是我试图通过 Kwarg
GADT 实现的目标。
Fun
class中的函数依赖是为了使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
,您仍然需要一个具有重叠实例的 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
即函数的类型取决于您想要的结果和您已经可以提供的参数。然后,根据手头的参数是否恰好是函数期望的第一个参数,您将不会有重叠的实例;相反,您将构建某种类型级别的映射,包括所有键控参数,并让函数接受一个这样的映射(或者,等效地,按字母顺序排列的所有参数)。