2017-12-27 20 views
2

私はさまざまな機能の長いリストを持つシステムを持っています。私はユーザ がシェルからこれらの関数にデータを渡すことができるようにしたいと思います。それらが渡すデータのタイプが間違っている場合、その関数が実行されるとエラーが表示されるはずです。Haskell:ラップされたデータへの関数の適用

データは、同じタイプとして一般的な方法で格納する必要があります。そのため、リストに格納してからexec関数に渡すことができます。

data Data = DInt Int | DBool Bool | DChar Char ..... 

Dataのリストをこのような関数に渡す方法はありますか?

exec :: [Data] -> (wrapped up function) -> Either Error Data 

関数はブール値を期待していたが、のIntが発見された、エラーがスローされた場合など

機能は、このアプリケーションを可能にした構造のいくつかの並べ替えに包まれなければならないだろうが、私はありませんよこの種の動作を実現する簡単な方法があるかどうかは確かです。

もう一度これを書き込もうとしていますので、何か明確にするようお願いします。

+0

あなたは 'Data.Dynamic'に精通していますか? –

答えて

2

readMaybeText.Readパッケージです。私は入力を読み込もうとしますが、返された場合はNothing別の型を解析しようとします。あなたはそうするように指示しなければなりません。例えば、Intまず、その後Bool、など私はあなたが求めている考える

http://hackage.haskell.org/package/base-4.10.1.0/docs/Text-Read.html#v:readMaybe

+1

この提案では、貧しい人の 'Data.Dynamic.Dynamic'として' String'を使用し、明示的な 'TypeRep'の代わりに' readMaybe'を解析します。データが文字列形式であれば良い戦略ですが、データが実際にはHaskellの値であれば、単に型を統一するために 'show'を呼び出すのはちょっと面倒です。 –

4

は完全に非慣用的です。私はあなたが望むものなら、あなたは間違った方法で問題を解決しているので、決して使うべきではない答えを提示するつもりです。

A悪いが楽しいソリューション

概要:我々が構築されますボックス - あらゆる型の値。これらのボックスには、関数のアプリケーションと戻り値の型がすべて正しいことを保証するために、等価チェックに使用できる値と型表現が含まれます。次に、関数を適用する前にタイプ表現(コンパイル時に失われた型を表す値)を手動でチェックします。関数と引数の型は不透明であることを覚えておいてください - コンパイル時に消去されているので、sin関数を使う必要がありますunsafeCoerce。ボックスは、私たちの実存である

{-# LANGUAGE ExistentialQuantification #-} 
{-# LANGUAGE TypeApplications #-} 
import Data.Typeable 
import Unsafe.Coerce 

data Box = forall a. Box (TypeRep, a) 

私たちは「安全APIとモジュールを作っていたので、もし私たちが実存種類、typeableと危険な強制が必要で開始する

dスマートなコンストラクタを作成したい:

-- | Convert a type into a "box" of the value and the value's type. 
mkBox :: Typeable a => a -> Box 
mkBox a = Box (typeOf a, a) 

exec関数今ではこの醜い和のタイプ(Data)のリストを取る必要はありませんが、ボックスの形でボックスと関数のリストを取ってから、各引数を1つずつ関数に適用して結果。呼び出し側は、戻り値の型(Proxy引数で示される)を静的に知る必要があることに注意してください。そうしないと、結果としてBoxを返さなければならなくなります。

exec :: Typeable a 
    => [Box] --^Arguments 
    -> Box --^Function 
    -> Proxy a 
    -> Either String a 
exec [] (Box (fTy,f)) p 
    | fTy == typeRep p = Right $ unsafeCoerce f 
    -- ^^ The function is fully applied. If it is the type expected 
    -- by the caller then we can return that value. 
    | otherwise  = Left "Final value does not match proxy type." 
exec ((Box (aTy,a)):as) (Box (fTy,f)) p 
    | Just appliedTy <- funResultTy fTy aTy = exec as (Box (appliedTy, (unsafeCoerce f) (unsafeCoerce a))) p 
    -- ^^ There is at least one more argument 
    | otherwise = Left "Some argument was the wrong type. XXX we can thread the arg number through if desired" 
    -- ^^ The function expected a different argument type _or_ it was fully applied (too many argument supplied!) 

私たちは、単に3つの結果をテストすることができます。

降伏
main :: IO() 
main = 
    do print $ exec [mkBox (1::Int), mkBox (2::Int)] (mkBox ((+) :: Int -> Int -> Int)) (Proxy @Int) 
    print $ exec [mkBox (1::Int)] (mkBox (last :: [Int] -> Int)) (Proxy @Int) 
    print $ exec [mkBox (1::Int)] (mkBox (id :: Int -> Int)) (Proxy @Double) 

Right 3 
Left "Some argument was the wrong type. XXX we can thread the arg number through if desired" 
Left "Final value does not match proxy type." 

EDITを:私はBoxと、このAPIは、より多くの教育やので、必要以上に少ない簡潔であることを言及する必要がありますがData.Dynamicを使用できます。たとえば、(プロキシーが推測できるのでAPIも変更しました):

{-# LANGUAGE ExistentialQuantification #-} 
{-# LANGUAGE GADTs #-} 
import Data.Dynamic 
import Type.Reflection 

type Box = Dynamic 

-- | Convert a type into a "box" of the value and the 
-- value's type. 
mkBox :: Typeable a => a -> Box 
mkBox = toDyn 

exec :: Typeable a 
    => [Box] --^Arguments 
    -> Box --^Function 
    -> Either String a 
exec [] f = case fromDynamic f of 
       Just x -> Right x 
       Nothing -> Left "Final type did not match proxy" 
exec (a:as) f 
    | Just applied <- dynApply f a = exec as applied 
    | otherwise = Left "Some argument was the wrong type. XXX we can thread the arg number through if desired" 


main :: IO() 
main = 
    do print (exec [mkBox (1::Int), mkBox (2::Int)] (mkBox ((+) :: Int -> Int -> Int)) :: Either String Int) 
    print (exec [mkBox (1::Int)] (mkBox (last :: [Int] -> Int)) :: Either String Int) 
    print (exec [mkBox (1::Int)] (mkBox (id :: Int -> Int)) :: Either String Double) 
+0

なぜあなたは 'unsafeCoerce'を使用していますか?古典的な 'Box'は' databox = foralla'です。あなたが 'cast'、' eqT'、または 'gcast'を使ってジョブを適切に行うための' Typeable a => Box a 'です。はい、それらは最終的に 'unsafeCoerce'(' Data.Typeable'の実装)上に構築されますが、それはユーザに関係するものではありません。もしあなたが好きなら、新しい 'Type.Reflection'を使って、あなたのスタイルのスタイルに似たものを適切に書くことができます。 – dfeuer

+0

実際、リフレクションモジュールは動的に使用するバージョンで暗黙的に使用します。 –

1

拡張子が1つのタイプクラスを使用するアプローチは次のとおりです。

{-# LANGUAGE FlexibleInstances #-} 

アイデアはFunction型クラス内execを定義することである:次いで

data Data = DInt Int | DBool Bool | DChar Char deriving (Show) 
data Error = TypeError String Data | MissingArg String | ExtraArgs 
      deriving (Show) 

class Function a where 
    exec :: a -> [Data] -> Either Error Data 

と1つのチェックを入力し、その型の引数を適用するために、各Dataコンストラクタのインスタンスの組を定義しますハンへ

instance Function r => Function (Int -> r) where 
    exec f (DInt x : xs) = exec (f x) xs 
    exec _ ( y : xs) = Left $ TypeError "DInt" y 
    exec _ []   = Left $ MissingArg "DInt" 

と別:、再帰的に残りの引数に移動するexecを評価しますそのタイプの「最終値を」DLE:

instance Function Int where 
    exec x [] = Right (DInt x) 
    exec _ _ = Left ExtraArgs 

あなたはBoolCharのための同様の定型および他のすべてのサポートされているタイプを必要としています。 (実際には、この決まり文句の多くはおそらく、および/またはおそらくIntBool、およびCharインスタンスを持つ二DataType型クラスを導入することにより、いくつかのヘルパー関数を用いて除去することができるが、私はそれを働いていない。)

instance Function r => Function (Bool -> r) where 
    exec f (DBool x : xs) = exec (f x) xs 
    exec _ (  y : xs) = Left $ TypeError "DBool" y 
    exec _ []    = Left $ MissingArg "DBool" 
instance Function Bool where 
    exec x [] = Right (DBool x) 
    exec _ _ = Left ExtraArgs 

instance Function r => Function (Char -> r) where 
    exec f (DChar x : xs) = exec (f x) xs 
    exec _ (  y : xs) = Left $ TypeError "DChar" y 
    exec _ []    = Left $ MissingArg "DChar" 
instance Function Char where 
    exec x [] = Right (DChar x) 
    exec _ _ = Left ExtraArgs 

、その後:あなたが書くことができるように

> exec f [DInt 1, DInt 2] 
Right (DInt 3) 
> exec g [DBool True, DInt 1, DInt 0] 
Right (DInt 1) 
> exec f [DInt 1, DChar 'a'] 
Left (TypeError "DInt" (DChar 'a')) 
> exec f [DInt 1] 
Left (MissingArg "DInt") 
> exec f [DInt 1, DInt 2, DInt 3] 
Left ExtraArgs 
> 

はおそらく、驚くほど、exec自体は、同じ型にこれらの関数をラップ:

> let myFunctions = [exec f, exec g] 
> :t myFunctions 
myFunctions :: [[Data] -> Either Error Data] 
> (myFunctions !! 0) [DInt 1, DInt 2] 
Right (DInt 3) 
> 

これらの関数をタイプ[Data] -> Either Error [Data]のファーストクラスの値として扱うことができます。

+0

したくない場合は、 'FlexibleInstances'を使う必要はありません。代わりに補助クラス 'class Arg aをexec = :: Function r =>(a - > r) - > [データ] - >いずれかのエラーデータ 'を追加してインスタンス'インスタンス(Arg a、関数r )=>関数(a - > r)ここで、exec = exec''です。 – dfeuer

関連する問題