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

时间:2018-02-12 21:04:40

标签: haskell types syntax typeclass

问题基本上是:我如何在Haskell中编写一个函数f,它接受​​一个值x和一个类型参数T,然后返回一个值y = f x T这取决于xT,而没有明确归因于整个表达式f x T的类型? (f x T不是有效的Haskell,而是占位符 - 伪语法)。

考虑以下情况。假设我有一个类型Transform a b,它提供单个函数transform :: a -> b。假设我还有instances Transform a btransform种类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个实例,以便它们从IntleftPath :: MigrationPath Int leftPath = migrate ((migrate ((Source "3.333") :: (MigrationPath String))) :: (MigrationPath Double)) 形成两个可能的(migrate ... (Type))。现在,我们(作为一个人)想要行使我们的自由意志,并强迫编译器选择左路径,或者在这个变换链中选择正确的路径。

在这种情况下,这甚至是可能的。我们可以通过构建一个&#34;洋葱来强制编译器创建正确的链。来自类型归属的约束:

Source

然而,由于两个原因,我发现它非常不理想:

  • AST 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;,因为我实际上确实有一个解决方案,它&从纯粹的语法角度来看,这只是相当丑陋。

2 个答案:

答案 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来翻转ba,以便TypeApplications首先实例化b。< / p>

答案 1 :(得分:5)

使用类型应用程序语法扩展。

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

即使纠正输入中的语法差异,即使在纠正输入中的语法差异后,也会仔细选择输入以使“与Double”注释相同。