Частичные функции Скалы в Хаскеле
Scala имеет очень хорошую поддержку частичных функций, главным образом потому, что в Scala, когда вы определяете частичную функцию, она также определяет isDefinedAt
функция для этого. А также Скала orElse
а также andThen
функции для работы с частичными функциями.
Haskell поддерживает частичные функции, просто не исчерпывающе определяя функцию (хотя в сообществе Haskell их категорически не рекомендуется). Но определить isDefinedAt
В общем, вы должны использовать какую-то обработку исключений, которую я не могу понять. однажды isDefinedAt
функция определяется, то ее можно использовать для определения orElse
а также andThen
функция уже там, как (.)
,
Короче говоря, я хочу определить функцию,
isDefinedAt :: (a -> b) -> a -> Bool
isDefinedAt f x = -- returns True if f is defined at x else False
Может кто-нибудь подскажите, пожалуйста, как можно написать такую функцию.
Обратите внимание, я могу определить функцию с подписью
isDefinedAt :: (a -> b) -> a -> IO Bool
для общего b
, Но я хочу функцию без ввода-вывода в совместном домене.
Хорошая статья о частичных функциях Scala - Как создавать и использовать частичные функции в Scala. Alvin Alexander
3 ответа
Я рекомендую, как и в Scala, использовать отдельный тип для частичных функций.
import Control.Arrow
import Data.Maybe
type Partial = Kleisli Maybe
isDefinedAt :: Partial a b -> a -> Bool
isDefinedAt f x = isJust $ runKleisli f x
-- laziness should save some of the work, if possible
orElse :: Partial a b -> Partial a b -> Partial a b
orElse = (<+>)
andThen :: Partial a b -> Partial b c -> Partial a c
andThen = (>>>)
Ваши версии isDefinedAt
это не то, что делает Scala (даже в подписи); это очень возможно (хотя и не рекомендуется) для PartialFunction
бросить исключение, когда isDefinedAt
правда. Или, когда вы определяете одно явно (не используя литерал), наоборот: apply
не должен бросать, когда isDefinedAt
ложно, это ответственность пользователя не вызывать его тогда. Таким образом, прямой эквивалент будет просто
data PartialFunction a b = PartialFunction { apply :: a -> b, isDefinedAt :: a -> Boolean }
что не особенно полезно.
apply
а также isDefinedAt
действительно связаны только в Scala для PartialFunction
литералы, которые требуют поддержки компилятора:
Я полагаю, что вы можете эмулировать это с помощью Template Haskell, но, честно говоря, думаю, что использование более похожего на Haskell подхода, описанного в ответе Даниэля Вагнера, лучше. Как он упоминает, лень помогает.
Хотя это работает еще лучше, если вы убедитесь, runKleisli f x
выполняется только один раз; оптимизировать случаи, когда у вас есть оба isDefinedAt
а также runKleisli
требует устранения общего выражения выражений, и компилятор с осторожностью относится к этому, см. При каких обстоятельствах устранение общего выражения может повлиять на лень программы на Haskell?
Вы можете сделать что-то вроде этого ( ОТКАЗ ОТ ОТВЕТСТВЕННОСТИ: я не проверял законы соответствующих классов типов и наличие строки в конструкторе для исключения в
Alternative
заставляет задуматься, законно ли это). Неоднородный Scala покрывается
fmap
; его однородный
andThen
/
compose
покрыты
>>>
/
<<<
из
Category
;
orElse
покрыто
<|>
;
lift
является
runToMaybe
.
Однако без глубокой интеграции компилятора, такой как в Scala, предупреждения о неполноте шаблона будут плохо взаимодействовать с этим. В Haskell есть только прагмы уровня модуля для этих вещей, и вы не захотите просто без разбора отключать их в любом модуле, где вы объявляете неисчерпаемые функции, иначе вы можете получить неприятные сюрпризы. В зависимости от вашего сценария использования оптика может показаться вам более идиоматичной и менее проблемной; вы можете создать шаблон с помощью Template Haskell.
(Примечание: я назвал это
Inexhaustive
потому что
PartialFunction
это что-то вроде неправильного названия, поскольку подразумевает, что это тотальный. Но в Scala нет средств проверки завершения или положительности, поэтому компилятор фактически не может говорить о целостности; поэтому вы получаете эту странную ситуацию, когда функция, которая не является частичной функцией, является просто «обычной», тогда как вы должны иметь возможность называть ее «общей
Function
". Вопрос здесь не в частичном или полном объеме, что является более широкой идеей, а в неисчерпаемости сопоставлений с образцом.)
{-# LANGUAGE TypeApplications #-}
module Inexhaustive
( Inexhaustive, inexhaustive
, runToMaybe
) where
import Prelude hiding ((.), id)
import Control.Applicative
import Control.Exception
import Control.Category
import Data.Maybe
import System.IO.Unsafe (unsafePerformIO)
newtype Inexhaustive a b = Inexhaustive (a -> b)
inexhaustive :: (a -> b) -> Inexhaustive a b
inexhaustive = Inexhaustive
runToMaybe :: Inexhaustive a b -> a -> Maybe b
runToMaybe (Inexhaustive f) x =
let io = fmap Just $ evaluate $ f x
in unsafePerformIO $ catch @PatternMatchFail io (\_ -> return Nothing)
isDefinedAt :: Inexhaustive a b -> a -> Bool
isDefinedAt f = isJust . runToMaybe f
instance Functor (Inexhaustive z) where
fmap f (Inexhaustive g) = inexhaustive (f . g)
instance Applicative (Inexhaustive z) where
pure x = inexhaustive (const x)
(Inexhaustive zab) <*> (Inexhaustive za) = Inexhaustive (\z -> zab z $ za z)
instance Alternative (Inexhaustive z) where
empty = inexhaustive (\_ -> throw $ PatternMatchFail "inexhaustive empty")
f <|> g =
inexhaustive $ \x ->
case runToMaybe f x <|> runToMaybe g x of
Just y -> y
instance Category Inexhaustive where
id = inexhaustive id
(Inexhaustive f) . (Inexhaustive g) = Inexhaustive (f . g)