Использовать параметр как шаблон в Haskell

Можно ли создать общую функцию, которая в качестве аргумента принимала бы Foo или Bar и возвращала бы функцию, которая использует этот аргумент в его сопоставлении с образцом?

Например, если у меня есть

isFoo :: SomeData -> Bool
isFoo (Foo _) = True
isFoo _       = False

isBar :: SomeData -> Bool
isBar (Bar _) = True
isBar _       = False

Есть ли способ создать общую функцию, что-то вроде

checkType :: SomeClass -> SomeData -> Bool
checkType (SomeClass _) = True
checkType _ = False

Я понимаю, что ситуация выглядит немного странно, а фактический прецедент немного сложнее, но проблема идентична.


Фактический код, который я пытаюсь реорганизовать, следующий

isString :: [LispVal] -> ThrowsError LispVal
isString [(String _)] = return $ Bool True
isString ((String _):xs) = isString xs >>= unpackBool >>= return . Bool
isString _ = return $ Bool False

isSymbol :: [LispVal] -> ThrowsError LispVal
isSymbol [(Atom _)] = return $ Bool True
isSymbol ((Atom _):xs) = isSymbol xs >>= unpackBool >>= return . Bool
isSymbol _ = return $ Bool False

isNumber :: [LispVal] -> ThrowsError LispVal
isNumber [(Number _)] = return $ Bool True
isNumber ((Number _):xs) = isNumber xs >>= unpackBool >>= return . Bool
isNumber _ = return $ Bool False

Итак, я хотел бы как-то сделать это более сухим

Ответ 1

Prism s из lens библиотека может выступать в качестве "первоклассных шаблонов", Чтобы определить призмы для вашего типа данных:

{-# LANGUAGE TemplateHaskell #-}

import Control.Lens

data SomeData = Foo Int 
              | Bar Char

-- Will create prisms named _Foo and _Bar
$(makePrisms ''SomeData)

Так как Prism действительны Fold s, мы можем передать их функции has из Control.Lens.Fold:

*Main> has _Foo (Foo 5)
True
*Main> has _Bar (Foo 5)
False

Еще одно интересное приложение призм в качестве первоклассных шаблонов - это "переопределение" поведения функции для случаев, когда аргумент соответствует призме. Вы можете использовать outside для Control.Lens.Prism. outside - это функция, которая принимает Prism и возвращает lens для функций, что позволяет вам "установить" специальный случай. Например:

functionToOverride :: SomeData -> Int
functionToOverride = const 5

-- If the arg is a Foo, return the contained int + 1 
newFunction :: SomeData -> Int
newFunction = functionToOverride & outside _Foo .~ succ

Тестирование обеих функций:

*Main> functionToOverride (Foo 77)
5
*Main> newFunction (Bar 'a')
5
*Main> newFunction (Foo 77)
78

Ответ 2

{-# LANGUAGE DeriveDataTypeable #-}
import Data.Data
import Data.Typeable

data Foo a = Foo1 a | Foo2 a deriving (Data, Typeable)

data Bar a = Bar1 a | Bar2 a deriving (Data, Typeable)

checkType :: Data a => Constr -> a -> Bool
checkType c v = c == toConstr v

Пример

zeroFoo1 = Foo1 (0 :: Int)

isFoo1 = checkType (toConstr zeroFoo1)

для обобщения по a вашего checkType, для каждого конструктора требуется постоянное значение (например, mempty).

(на самом деле, единственный трюк: toConstr a == toConstr b)

Ответ 3

В настоящее время это невозможно, хотя некоторые расширения, которые позволили бы ему работать,

Ближайшим обходным решением в настоящее время является, вероятно, функция, которая соответствует соответствующему шаблону:

isString :: [LispVal] -> ThrowsError LispVal
isString [(String _)] = return $ Bool True
isString ((String _):xs) = isString xs >>= unpackBool >>= return . Bool
isString _ = return $ Bool False

Вы можете заменить совпадение верхнего шаблона функцией:

isLispVal :: (LispVal -> Bool) -> [LispVal] -> ThrowsError LispVal
isLispVal p [x] | p x = return $ Bool True
isLispVal p (x:xs) | p x = isLispVal p xs >>= unpackBool >>= return . Bool
isLispVal p _ = return $ Bool False

Когда я это сделал, я часто нуждался в правильных объективах вместо функций предиката, но это зависит от варианта использования.

Ответ 4

Кажется, что ваша функция isString - это просто функция all.

Рассмотрим это:

data LispVal = Str String | B Bool | Sym String | Num Integer

isLispStr (Str _) = True
isLispStr _ = False

isLispNum (Num _) = True
isLispNum _ = False

isLispSym (Sym _) = True
isLispSym _ = False

-- etc. for the other LispVal constructors.

Теперь рассмотрим эти функции:

isString' :: [LispVal] -> LispVal
isString' = B . all isLispStr

isSymbol' :: [LispVal] -> LispVal
isSymbol' = B . all isLispSym

-- ...

Это "чистые" (т.е. не монадические) версии ваших функций isString и isSymbol. Монадические версии:

isString = return . isString'
isSymbol = return . isSymbol'

и др.