And*_*kin 2 syntax haskell types typeclass
问题基本上是:如何f在Haskell中编写一个带有值x和类型参数的函数T,然后返回一个y = f x T 同时依赖于x和的值T,而不显式地归为整个表达式的类型f x T?(这f x T不是有效的Haskell,而是占位符 - 伪语法).
考虑以下情况.假设我有一个Transform a b提供单个函数的类型类transform :: a -> b.假设我也有一堆instances的Transform的类型的各种组合a b.现在我想将多功能transform连接在一起.但是,我希望Transform根据先前构造的链和转换链中的下一个类型来选择-instance .理想情况下,这会给我这样的东西(假设的函数source和"传递类型参数"的migrate无效语法<< >>; migrate用作中缀操作):
z = source<<A>> migrate <<B>> ... migrate <<Z>>
Run Code Online (Sandbox Code Playgroud)
在这里,source以某种方式生成类型的值A,并且每个migrate<<T>>都应该找到一个实例Transform S T并将其附加到链.
到目前为止我想出了什么:它实际上(几乎)使用类型归属在Haskell中工作.考虑以下(可编译)示例:
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE ExistentialQuantification #-}
-- compiles with:
-- The Glorious Glasgow Haskell Compilation System, version 8.2.2
-- A typeclass with two type-arguments
class Transform a b where
transform :: a -> b
-- instances of `T` forming a "diamond"
--
-- String
-- / \
-- / \
-- / \
-- / \
-- Double Rational
-- \ /
-- \ /
-- \ /
-- \ /
-- Int
--
instance Transform String Double where
transform = read
instance Transform String Rational where
transform = read -- turns out to be same as fo `Double`, but pretend it's different
instance Transform Double Int where
transform = round
instance Transform Rational Int where
transform = round -- pretend it's different from `Double`-version
-- A `MigrationPath` to `b` is
-- essentially some data source and
-- a chain of transformations
-- supplied by typeclass `T`
--
-- The `String` here is a dummy for a more
-- complex operation that is roughly `a -> b`
data MigrationPath b = Source b
| forall a . Modify (MigrationPath a) (a -> b)
-- A function that appends a transformation `T` from `a` to `b`
-- to a `MigrationPath a`
migrate :: Transform a b => MigrationPath a -> MigrationPath b
migrate mpa = Modify mpa transform
-- Build two paths along the left and right side
-- of the diamond
leftPath :: MigrationPath Int
leftPath = migrate ((migrate ((Source "3.333") :: (MigrationPath String))) :: (MigrationPath Double))
rightPath :: MigrationPath Int
rightPath = migrate((migrate ((Source "10/3") :: (MigrationPath String))) :: (MigrationPath Rational))
main = putStrLn "it compiles, ship it"
Run Code Online (Sandbox Code Playgroud)
在这个例子中,我们定义Transform实例,使得其形成两种可能MigrationPath从s String到Int.现在,我们(作为一个人)想要行使我们的自由意志,并迫使编译器选择左路径或这个变换链中的正确路径.
在这种情况下,这甚至是可能的.我们可以强制编译器通过从类型归属构造约束的"洋葱"来创建正确的链:
leftPath :: MigrationPath Int
leftPath = migrate ((migrate ((Source "3.333") :: (MigrationPath String))) :: (MigrationPath Double))
Run Code Online (Sandbox Code Playgroud)
但是,由于两个原因,我发现它非常不理想:
(migrate ... (Type))围绕着两侧增长Source(这是一个小问题,它可能可以使用具有左关联性的中缀运算符来纠正).MigrationPath存储的类型不仅是目标类型,而且是源类型,使用类型 - 归属方法,我们将不得不重复链中的每个类型两次,这将使整个方法使用起来太尴尬.问题:有没有办法构建上述转换链,只有"下一个类型",而不是整个"类型MigrationPath T"必须归属?
我不是要问:我很清楚,在上面的玩具示例中,定义函数transformStringToInt :: String -> Int等更容易,然后使用它们将它们链接在一起..这不是问题.问题是:如何强制编译器生成与transformStringToInt我指定类型时相对应的表达式.在实际应用程序中,我只想指定类型,并使用一组相当复杂的规则来使用right transform-function 派生适当的实例.
(可选):只是给人一种我正在寻找的印象.以下是一个完全类似的例子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]
Run Code Online (Sandbox Code Playgroud)
请注意migrate-method除了"next type"之外什么都不需要,而不是迄今为止构造的整个表达式的类型归属.
相关:我想要指出,这个问题并不完全与"将类型作为Haskell中函数的参数传递?"完全相同..我的用例有点不同.我也倾向于不同意那里的答案"它不可能/你不需要它",因为我确实有一个解决方案,从纯粹的语法观点来看它只是相当丑陋.
Ale*_*ing 10
使用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
Run Code Online (Sandbox Code Playgroud)
该定义transformTo使用显式使用forall来翻转b,a以便首先TypeApplications实例化b.
使用类型应用程序语法扩展.
> :set -XTypeApplications
> transform @_ @Int (transform @_ @Double "9007199254740993")
9007199254740992
> transform @_ @Int (transform @_ @Rational "9007199254740993%1")
9007199254740993
Run Code Online (Sandbox Code Playgroud)
Double即使在纠正输入中的语法差异之后,也会仔细选择输入以使"与"注释相同.