我正在尝试在Haskell中实现关键字参数,类似于Ocaml中的参数。我的目标是拥有可以按任何顺序传递的参数,并且可以部分地应用于函数调用中(产生一个带有剩余关键字参数的新函数)。
我尝试使用DataKinds和一个类型类来实现这一点,类型类表示可以转换为函数a -> b
的值。"我们的想法是,一个带有两个关键字参数foo
和bar
的函数可以转换为看似foo -> bar -> 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
答案 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
即。函数的类型取决于您想要的结果和您已经可以提供的参数。根据手头的参数是否恰好是函数所期望的第一个,您将不会有重叠的实例;相反,你会构建某种类型级别的映射,包括所有键控参数,并让函数接受一个这样的映射(或者,等价地,所有参数按字母顺序)。