Haskell:接受类型参数并根据该类型返回值的函数?

Haskell:接受类型参数并根据该类型返回值的函数?,haskell,types,syntax,typeclass,Haskell,Types,Syntax,Typeclass,问题基本上是:如何在Haskell中编写一个函数f,它接受一个值x和一个类型参数T,然后返回一个值y=f x T,该值既依赖于x又依赖于T,而不明确地归属整个表达式的类型f x T?(f x T不是有效的Haskell,而是占位符伪语法) 考虑以下情况。假设我有一个typeclasstransformabb,它提供一个函数Transform::a->b。假设我还有一堆转换的实例,用于各种类型的组合ab。现在,我想将多个transform-函数链接在一起。但是,我希望根据先前构造的链和转换链中的下

问题基本上是:如何在Haskell中编写一个函数
f
,它接受一个值
x
和一个类型参数
T
,然后返回一个值
y=f x T
,该值既依赖于
x
又依赖于
T
,而不明确地归属整个表达式的类型
f x T
?(f x T不是有效的Haskell,而是占位符伪语法)

考虑以下情况。假设我有一个typeclass
transformabb
,它提供一个函数
Transform::a->b
。假设我还有一堆
转换的
实例
,用于各种类型的组合
ab
。现在,我想将多个
transform
-函数链接在一起。但是,我希望根据先前构造的链和转换链中的下一种类型来选择
转换
-实例。理想情况下,这会给我类似的东西(假设函数
source
migrate
和无效语法
“传递类型参数”;
migrate
用作中缀操作):

在本例中,我们定义了
Transform
实例,以便它们形成从
String
Int
的两个可能的
MigrationPath
s。现在,我们(作为一个人)想要行使我们的自由意志,强迫编译器在这个转换链中选择左路径或右路径

在这种情况下,这甚至是可能的。我们可以通过从类型属性构造约束的“洋葱”来强制编译器创建正确的链:

leftPath :: MigrationPath Int
leftPath = migrate ((migrate ((Source "3.333") :: (MigrationPath String))) :: (MigrationPath Double))
然而,我发现它非常次优,原因有两个:

  • AST
    (migrate…(Type))
    源代码的两侧增长(这是一个小问题,可能可以使用具有左关联性的中缀运算符进行纠正)
  • 更严重的是:如果
    MigrationPath
    的类型不仅存储了目标类型,而且还存储了源类型,那么使用类型归属方法,我们将不得不重复链中的每个类型两次,这将使整个方法难以使用
问题:是否有任何方法来构建上述转换链,从而只需指定“下一种类型”,而不必指定整个“迁移路径T的类型”

我没有问的是:我很清楚,在上面的玩具示例中,定义函数
transformStringToInt::String->Int
等更容易,然后使用
将它们链接在一起。这不是问题所在。问题是:当我只指定类型时,如何强制编译器生成与
transformStringToInt
对应的表达式。在实际的应用程序中,我只想指定类型,并使用一组相当复杂的规则,通过正确的
transform
-函数派生适当的实例


(可选):只是给人一个印象,我在寻找什么。下面是来自Scala的一个完全类似的例子:

 // typeclass providing a transformation from `X` to `Y`
 trait Transform[X, Y] {
   def transform(x: X): Y
 }

 // Some data migration path ending with `X`
 sealed trait MigrationPath[X] {
   def migrate[Y](implicit t: Transform[X, Y]): MigrationPath[Y] = Migrate(this, t)
 }
 case class Source[X](x: X) extends MigrationPath[X]
 case class Migrate[A, X](a: MigrationPath[A], t: Transform[A, X]) extends MigrationPath[X]


 // really bad implementation of fractions
 case class Q(num: Int, denom: Int) {
   def toInt: Int = num / denom
 }

 // typeclass instances for various type combinations
 implicit object TransformStringDouble extends Transform[String, Double] {
   def transform(s: String) = s.toDouble
 }

 implicit object TransformStringQ extends Transform[String, Q] {
   def transform(s: String) = Q(s.split("/")(0).toInt, s.split("/")(1).toInt)
 }

 implicit object TransformDoubleInt extends Transform[Double, Int] {
   def transform(d: Double) = d.toInt
 }

 implicit object TransformQInt extends Transform[Q, Int] {
   def transform(q: Q) = q.toInt
 }

 // constructing migration paths that yield `Int`
 val leftPath = Source("3.33").migrate[Double].migrate[Int]
 val rightPath = Source("10/3").migrate[Q].migrate[Int]
请注意,
migrate
-方法只需要“下一个类型”,而不是迄今为止构造的整个表达式的类型归属



相关:我想指出,这个问题并不是完全重复的。我的用例有点不同。我也倾向于不同意那里的答案“这不可能/你不需要它”,因为我确实有一个解决方案,从纯语法的角度来看,它相当难看。

使用
TypeApplications
语言扩展,它允许你显式地实例化单个类型变量。下面的代码似乎具有您想要的风格,并进行了类型检查:

{-# LANGUAGE ExplicitForAll, FlexibleInstances, MultiParamTypeClasses, TypeApplications #-}

class Transform a b where
  transform :: a -> b

instance Transform String Double where
  transform = read

instance Transform String Rational where
  transform = read

instance Transform Double Int where
  transform = round

instance Transform Rational Int where
  transform = round

transformTo :: forall b a. Transform a b => a -> b
transformTo = transform

stringToInt1 :: String -> Int
stringToInt1 = transform . transformTo @Double

stringToInt2 :: String -> Int
stringToInt2 = transform . transformTo @Rational

定义
transformTo
使用
forall
来翻转
b
a
,以便
TypeApplications
首先实例化
b

使用TypeApplications语法扩展

> :set -XTypeApplications
> transform @_ @Int (transform @_ @Double "9007199254740993")
9007199254740992
> transform @_ @Int (transform @_ @Rational "9007199254740993%1")
9007199254740993

即使在纠正了输入中的语法差异之后,仔细选择的输入也会给你的“Double
”注释带来谎言。

哦,天哪,你链接的问题有点过时了。现在有一个语言扩展。@HTNW如果它可以通过一行简单的语言扩展来解决,我就接受它;)很有可能我的Haskell不是最新的,我目前正在研究一段非常特定的代码是否可以从Scala转换为Haskell而不费吹灰之力。您可以使用显式的
for all
来设置参数的顺序。我还认为,对于任何要与
TypeApplications
@liyaoxia一起使用的函数来说,这都应该是强制性的,当然可以,或者首先以其他顺序给出typeclass参数。另一方面,不管怎样,了解类型孔应用程序语法是很方便的,因为我不清楚为什么返回类型显然是消除
transform
歧义的正确方法。就我所知,在代码的其他部分,人们可能还想消除输入类型的歧义。这似乎正是我想要的。翻转类型参数的好技巧;非常感谢。
{-# LANGUAGE ExplicitForAll, FlexibleInstances, MultiParamTypeClasses, TypeApplications #-}

class Transform a b where
  transform :: a -> b

instance Transform String Double where
  transform = read

instance Transform String Rational where
  transform = read

instance Transform Double Int where
  transform = round

instance Transform Rational Int where
  transform = round

transformTo :: forall b a. Transform a b => a -> b
transformTo = transform

stringToInt1 :: String -> Int
stringToInt1 = transform . transformTo @Double

stringToInt2 :: String -> Int
stringToInt2 = transform . transformTo @Rational
> :set -XTypeApplications
> transform @_ @Int (transform @_ @Double "9007199254740993")
9007199254740992
> transform @_ @Int (transform @_ @Rational "9007199254740993%1")
9007199254740993