7

TL; DR:異種レコードのさまざまなフィールドから少数のデータ型(おそらくDoubleとBoolのみ)の1つを返すコードを生成する方法を理解するのに助けが必要です。

長い形式:次のデータ型を想定

data Circle = Circle { radius :: Integer, origin :: Point }
data Square = Square { side  :: Integer }

といくつかの定型コード

circle = Circle 3 (Point 0 0)
square = Square 5

私は小さなDSLを構築していて、ユーザーに次のようなものを書いてもらいたい

circle.origin
square.side

そしてそれは同様のコードを生成します

origin . circle
side . square

これを解析する場合、たとえば「circle」と「origin」という文字列があります。これらを関数呼び出しに変換する必要があります。私は明らかにこのようなものを持つことができます:

data Expr a = IntegerE (a -> Integer)
            | PointE (a -> Point)

lookupF2I "side"   = Just $ IntegerE side
lookupF2I "radius" = Just $ IntegerE radius
lookupF2I _        = Nothing

lookupF2P "origin" = Just $ PointE origin
lookupF2P _ = Nothing

返されたデータ型ごとに1つのルックアップ関数があります。データ型ごとに1つの関数を持つことは、実際には2つまたは3つのデータ型しか処理しないという点で、DSLの観点からは実用的です。しかし、これは特に効果的な方法とは思えません。これを(確かに)行うためのより良い方法はありますか?そうでない場合は、フィールドをルックアップできるようにしたいさまざまなレコードから、さまざまなルックアップ関数のコードを生成する方法はありますか?

"circle"第二に、解析された、または"square"適切な関数を呼び出す必要がcircleあるという問題がまだありsquareます。型クラスを使用してこれを実装する場合、次のようなことができます。

instance Lookup Circle where
    lookupF2I "radius" = Just $ IntegerE radius
    lookupF2I _        = Nothing
    lookupF2P "origin" = Just $ PointE origin
    lookupF2P _        = Nothing

しかし、その場合、ルックアップ関数に適用するタイプを把握する必要があり、さらに悪いことに、これを使用する(多くの)レコードごとにインスタンスを手書きする必要があります。

注:単一のADTを使用して表現できるという事実はCircle、これが不自然な例であるという点で私の質問に付随しています。Square実際のコードには、さまざまな非常に異なるレコードが含まれますが、それらに共通するのは、同じタイプのフィールドを持つことだけです。

4

1 に答える 1

1

テンプレートHaskellを使用して、この問題を解決するための優れたタイプセーフな方法を提供してみました。これを行うために、指定された文字列から式を作成しました。

Lensパッケージでそれができると思いますが、もっとシンプルで柔軟なソリューションかもしれません。

次のように使用できます。

import THRecSyntax
circleOrigin = compDSL "circle.origin.x"

そして、次のように定義されます。

{-# LANGUAGE TemplateHaskell #-}
import Language.Haskell.TH

compDSL :: String -> Q Exp
compDSL s = return 
            $ foldr1 AppE 
            $ map (VarE . mkName) 
            (reverse $ splitEvery '.' s)

したがって、結果の式は次のようになります。x (origin circle)

注:splitEveryは、リストをサブリストに分割して、指定された要素を取り出す関数です。実装例:

splitEvery :: Eq a => a -> [a] -> [[a]]
splitEvery elem s = splitter (s,[])
  where splitter (rest, res) = case elemIndex elem rest of
            Just dotInd -> let (fst,rest') = splitAt dotInd rest
                            in splitter (tail rest', fst : res)
            Nothing -> reverse (rest : res)

これは、指定された構文で組み込みDSLを作成するための、重量はありますがタイプセーフな方法です。

于 2013-07-21T14:28:49.553 に答える