どちらかの両側にポリモーフィック関数を適用するにはどうすればよいですか?

Dec 06 2020

私はこれを試しました:

type TestT = Either Int Float

testM :: (a -> a) -> TestT -> TestT
testM f (Left x) = Left (f x)
testM f (Right x) = Right (f x)

しかし、それは機能しません、これを行う方法はありますか?私は周りを見回しましたが、似たようなものはすべて本当に複雑で限られていました。

要求されたエラーメッセージ:

Main.hs:101:28: error:
    • Couldn't match expected type ‘a’ with actual type ‘Int’
      ‘a’ is a rigid type variable bound by
        the type signature for:
          testM :: forall a. (a -> a) -> TestT -> TestT
        at Main.hs:100:1-35
    • In the first argument of ‘f’, namely ‘x’
      In the first argument of ‘Left’, namely ‘(f x)’
      In the expression: Left (f x)
    • Relevant bindings include
        f :: a -> a (bound at Main.hs:101:7)
        testM :: (a -> a) -> TestT -> TestT (bound at Main.hs:101:1)

回答

3 jpmarinier Dec 06 2020 at 03:30

基本言語ではそれができないと思います。コメントで述べたように、RankNTypesなどのいくつかの拡張機能を有効にする必要がある場合があります。

関係するすべての型は数値型であるため、ポリモーフィック関数として(+1)などのインクリメント関数を使用したくなります。

下で試してみましょうghci

$ ghci
GHCi, version 8.6.5: http://www.haskell.org/ghc/  :? for help
 λ> 
 λ> type TestT = Either Int Float 
 λ> 
 λ> :set +m
 λ> 
 λ> :set -XRankNTypes
 λ> :set -XScopedTypeVariables
 λ> 
 λ> {-
|λ> let { testM :: (forall a. Num a => a -> a) -> TestT -> TestT ;
|λ>       testM fn (Left x) = Left (fn x) ;
|λ>       testM fn (Right x) = Right (fn x) }
|λ> -}
 λ> 
 λ> :type testM
testM :: (forall a. Num a => a -> a) -> TestT -> TestT
 λ> 
 λ> testM (+3) (Left 42)
Left 45
 λ> 
 λ> testM (+3) (Right 3.14159)
Right 6.14159
 λ> 

注1:言語拡張子を省略すると、RankNTypesを示唆するメッセージが表示されて壊れます。

注2:のforall a. Num a => (a -> a)代わりに使用すると(forall a. Num a => a -> a)、壊れます。

注3:ここにいくつかの先行技術:SO-q38298119とAlexisKingからの有益なコメント。

2 DavidFox Dec 09 2020 at 10:26

これを行う1つの方法は、Bifunctorを使用することです。

Prelude> :m +Data.Bifunctor
Prelude Data.Bifunctor> bimap show show (Left 3)
Left "3"
Prelude Data.Bifunctor> bimap show show (Right 'x')
Right "'x'"
Prelude Data.Bifunctor>