找到你要的答案

Q:Choose a typeclass based on return type

Q:选择一个typeclass基于返回类型

I want to be able to have a function, which implementation will choose a typeclass based on the manual type specification of it's return type.

Here's a contrived example: a typeclass and two instances:

class ToString a where
  toString :: a -> String

instance ToString Double where
  toString = const "double"

instance ToString Int where
  toString = const "int"

I'm able to choose the instance by calling the toString with Int type:

function :: String
function = toString (undefined :: Int)

So far so good. What I'd like to do is to be able to write the function, so it will work for any a if there exists a typeclass for it:

function' :: (ToString a) => String
function' = toString (undefined :: a)

Here, the function' doesn't compile, because the a type parameter is not mentioned anywhere in the signature and it's not possible to specify the typeclass upon calling.

So it looks like the only option is to pass the type information about the type of a into the return type:

data Wrapper a = Wrapper {
  field :: String }

function'' :: (ToString a) => Wrapper a
function'' = Wrapper $ toString (undefined :: a)

showToString :: String
showToString = field (function'' :: Wrapper Int)

I define a Wrapper type just for carrying the type information. In the showToString, my hope is that since I specify the exact type of Wrapper, then the typechecker can infer that the a in function'' is and Int and pick the Int instance of the ToString typeclass.

But the reality doesn't correspond with my hopes, this is the message from compiler

Could not deduce (ToString a0) arising from a use of `toString'

Is there a way, how to convince the compiler, that he can pick the right typeclass in the function'', because I specify the it by having the type declaration of :: Wrapper Int?

我希望能够有一个功能,它的实施将选择一个typeclass基于手动式规范它的返回类型。

这里有一个例子:一个typeclass两实例:

class ToString a where
  toString :: a -> String

instance ToString Double where
  toString = const "double"

instance ToString Int where
  toString = const "int"

我可以通过调用toString Int type选择的实例:

function :: String
function = toString (undefined :: Int)

到现在为止,一直都还不错.我想做的就是能写的功能,因此,如果存在一个typeclass任何工作:

function' :: (ToString a) => String
function' = toString (undefined :: a)

在这里,函数不能编译,因为类型参数不在任何地方提到签名和指定typeclass召唤是不可能的。

所以看起来唯一的选择是将类型的类型信息传递给返回类型:

data Wrapper a = Wrapper {
  field :: String }

function'' :: (ToString a) => Wrapper a
function'' = Wrapper $ toString (undefined :: a)

showToString :: String
showToString = field (function'' :: Wrapper Int)

我只定义一个包装类型,用于携带类型信息。在showtostring,我希望因为我指定包装的确切类型,然后typechecker可以推断出一个功能”和Int接int实例ToString typeclass。

但现实并不符合我的希望,这是来自编译器的信息

无法推断(toString A0)所产生的使用` toString”

有没有一种方式,如何让编译器,他能选择正确的typeclass在功能',因为我指定它的类型声明::包装int?

answer1: 回答1:

First, let me suggest that instead of an own Wrapper type, you use Data.Tagged.Tagged, whose purpose is exactly this kind of stuff.

Apart from that, you need to turn on the -XScopedTypeVariables extension, otherwise the a type variable only exists in the type signature itself, but not in signatures of local bindings.

{-# LANGUAGE ScopedTypeVariables #-}

import Data.Tagged

function''' :: forall a. ToString a => Tagged a String
function''' = Tagged $ toString (undefined :: a)

The explicit forall is necessary for a to actually become a scoped variable, otherwise the extension doesn't kick in.

However....

Actually, the best thing would probably to have the class method produce a tagged value in the first place:

class NamedType a where
  typeName :: Tagged a String

instance NamedType Double where
  typeName = Tagged "double"
instance NamedType Int where
  typeName = Tagged "int"
...

Or don't write your own class at all but use typeable:

import Data.Typeable

typeName' :: Typeable a => Tagged a String
typeName' = fmap show $ unproxy typeRep

Of course, that will give you the actual uppercase type names, and it might work for types you don't actually want it to.

首先,我认为,不是自己的包装类型,你用data.tagged.tagged,其目的正是这种东西。

除此之外,你需要打开XScopedTypeVariables延伸,否则一个类型变量只存在于型签名本身,而不是在本地绑定签名。

{-# LANGUAGE ScopedTypeVariables #-}

import Data.Tagged

function''' :: forall a. ToString a => Tagged a String
function''' = Tagged $ toString (undefined :: a)

明确的人是一个真正成为一个限定了作用域的变量是必要的,否则延伸不踢。

However....

实际上,最好的方法可能是类方法首先产生一个标记值:

class NamedType a where
  typeName :: Tagged a String

instance NamedType Double where
  typeName = Tagged "double"
instance NamedType Int where
  typeName = Tagged "int"
...

还是不要写自己的类只使用可分:

import Data.Typeable

typeName' :: Typeable a => Tagged a String
typeName' = fmap show $ unproxy typeRep

当然,这会给你实际的大写的类型名称,它会为你不工作却要它。

answer2: 回答2:

leftaroundabout's answer is probably the one you want. But for completeness, here's one more thing you can do:

unwrap :: Wrapper a -> a
unwrap = error "can't actually unwrap"

function'' :: (ToString a) => Wrapper a
function'' = x
  where
    x = Wrapper (toString (unwrap x))

The idea is that I want an a to pass to toString but only Wrapper a shows up in my type. So I just define a function which takes Wrapper a and produces a – such a function can't have a real implementation, but we're not using it for its return value anyway – and apply it to the Wrapper a thing.

There's a bit of additional awkwardness because Wrapper a shows up in the result instead of an argument, but this (slightly silly) recursion takes care of that.

leftaroundabout的答案可能是你想要的。但为了完整,这里还有一件事你可以做:

unwrap :: Wrapper a -> a
unwrap = error "can't actually unwrap"

function'' :: (ToString a) => Wrapper a
function'' = x
  where
    x = Wrapper (toString (unwrap x))

这个想法是,我想要一个通过ToString只有包装的出现在我的类型。因此,我只是定义了一个函数,它需要包装一个并产生一个这样的函数不能有一个真正的实现,但我们不使用它的返回值无论如何-并将其应用到包装一件事。

有一点额外的尴尬因为包装出现在结果而不是一个论点,但这(有点傻)递归的照顾。

haskell  typeclass