Частичные функции Скалы в Хаскеле

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 литералы, которые требуют поддержки компилятора:

Значение PartialFunction получает дополнительный член isDefinedAt, который получается из сопоставления с образцом в литерале функции, причем тело каждого случая заменяется на true, и добавляется значение по умолчанию (если не было задано), которое оценивается как false.

Я полагаю, что вы можете эмулировать это с помощью 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)

Другие вопросы по тегам