问题基本上是:我如何在Haskell中编写一个函数f
,它接受一个值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
的各种组合。现在,我想将多个source
- 函数链接在一起。但是,我希望根据先前构建的链和转换链中的 next 类型选择migrate
- 实例。理想情况下,这会给我这样的东西(假设函数<< >>
和migrate
和无效语法z = source<<A>> migrate <<B>> ... migrate <<Z>>
for&#34;传递类型参数&#34 ;; source
是用作中缀操作):
A
在这里,migrate<<T>>
以某种方式生成Transform S T
类型的值,每个{-# 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"
应该找到一个实例Transform
并将其附加到链中。
到目前为止我想出了什么:它实际上(几乎)使用类型归属在Haskell中工作。请考虑以下(可编译)示例:
MigrationPath
在此示例中,我们定义String
个实例,以便它们从Int
到leftPath :: MigrationPath Int
leftPath = migrate ((migrate ((Source "3.333") :: (MigrationPath String))) :: (MigrationPath Double))
形成两个可能的(migrate ... (Type))
。现在,我们(作为一个人)想要行使我们的自由意志,并强迫编译器选择左路径,或者在这个变换链中选择正确的路径。
在这种情况下,这甚至是可能的。我们可以通过构建一个&#34;洋葱来强制编译器创建正确的链。来自类型归属的约束:
Source
然而,由于两个原因,我发现它非常不理想:
MigrationPath
增长到MigrationPath T
周围的两侧(这是一个小问题,可能可以使用具有左关联性的中缀运算符来纠正)。transformStringToInt :: String -> Int
的类型不仅存储了目标类型,还存储了源类型,使用类型归属方法我们将不得不重复链中的每个类型> em>,这会使整个方法难以使用。 问题:有没有办法构建上述转换链,只有&#34;下一个类型&#34;而不是整个&#34;类型的.
&#34;必须归因?
我没有问过什么:我很清楚,在上面的玩具示例中,定义函数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
:
switch
请注意public class SummeyAct extends AppCompatActivity {
Context context;
FirebaseAuth firebaseAuth;
private DrawerLayout drawerLayout;
private ActionBarDrawerToggle actionBarDrawerToggle;
Intent intent;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_summey);
drawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
actionBarDrawerToggle = new ActionBarDrawerToggle(this ,drawerLayout, R.string.open, R.string.close);
drawerLayout.addDrawerListener(actionBarDrawerToggle);
actionBarDrawerToggle.syncState();
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
context = this;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
Log.d("menu id",String.valueOf(item.getItemId()));
switch (item.getItemId()) {
case R.id.PlayerListMenu:
Intent intent = new Intent(context, Players.class);
startActivity(intent);
return true;
case R.id.FanChatMenu:
Intent intent2 = new Intent(context, Players.class);
startActivity(intent2);
return true;
case R.id.SighnOutMenu:
LogOutMethod();
return true;
}
if(actionBarDrawerToggle.onOptionsItemSelected(item))
{
return true;
}
return super.onOptionsItemSelected(item);
}
- 方法除了&#34; next type&#34;之外什么都不需要,而不是到目前为止构造的整个表达式的类型归属。
相关:我想注意这个问题与"Pass Types as arguments to a function in Haskell?"不完全相同。我的用例有点不同。我也倾向于不同意那里的答案&#34;它不可能/你不需要它&#34;,因为我实际上确实有一个解决方案,它&从纯粹的语法角度来看,这只是相当丑陋。
答案 0 :(得分: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
定义transformTo
使用明确使用forall
来翻转b
和a
,以便TypeApplications
首先实例化b
。< / p>
答案 1 :(得分:5)
使用类型应用程序语法扩展。
> :set -XTypeApplications
> transform @_ @Int (transform @_ @Double "9007199254740993")
9007199254740992
> transform @_ @Int (transform @_ @Rational "9007199254740993%1")
9007199254740993
即使纠正输入中的语法差异,即使在纠正输入中的语法差异后,也会仔细选择输入以使“与Double
”注释相同。