순수 함수형 언어에서 역함수를 얻는 알고리즘이 있습니까?


100

Haskell과 같은 순수 함수 언어에서 함수의 역수를 얻는 알고리즘이 있습니까? (편집) 그것이 bijective 일 때? 기능을 프로그래밍하는 특정 방법이 있습니까?


5
수학적으로의 경우 f x = 11의 역수는 정수 세트이고 다른 것의 역수는 빈 세트 라고 말하는 것은 잘못된 것이 아닙니다 . 일부 답변이 뭐라고 말하든간에, bijective가 아닌 기능은 가장 큰 문제가 아닙니다.
Karolis Juodelė 2011

2
정답은 '예'이지만 효율적이지 않습니다. f : A-> B와 A를 유한하게하고 b € B가 주어지면 f (a) = b 인 모든 a € A를 찾기 위해 모든 f (A)를 "단지"검사해야합니다. 양자 컴퓨터에서는 아마도 O (크기 (a)) 복잡성이있을 것입니다. 물론 실용적인 알고리즘을 찾습니다. 그것은 아니다 (이 O (2 ^ 크기의 (a))), 존재하지만 ...
josejuan

QuickCheck는 정확히 수행합니다 (f : A-> Bool에서 False를 찾습니다).
josejuan

4
@ KarolisJuodelė : 동의하지 않습니다. 그것은 일반적으로 역의 의미가 아닙니다. 꽤 많이 나는 용어가 발생할 때마다의 역은 f함수는 g그러한 f . g = idg . f = id. 당신의 후보자는이 경우에 타이프 체크조차하지 않습니다.
Ben Millwood 2012

3
@BenMillwood, 당신 말이 맞아요. 제가 말한 것은 역함수가 아니라 역상이라고합니다. 내 요점은 f x = 1역이없는 답변 은 매우 좁은 접근 방식을 취하고 문제의 전체 복잡성을 무시한다는 것입니다.
Karolis Juodelė 2011

답변:


101

어떤 경우에는 그렇습니다! Bidirectionalization for Free 라는 아름다운 논문이 있습니다 ! 여기서는 함수가 충분히 다형성 인 경우에 대해 완전히 자동으로 역함수를 유도 할 수있는 몇 가지 경우를 설명합니다. (또한 함수가 다형성이 아닐 때 문제를 어렵게 만드는 요인에 대해서도 설명합니다.)

함수가 가역적 일 경우 얻을 수있는 것은 역 (스퓨리어스 입력)입니다. 다른 경우에는 이전 입력 값과 새 출력 값을 "병합"하려는 함수를 얻습니다.


3
다음은 양방향화의 최첨단을 조사하는 최신 논문입니다. 여기에는 "구문"및 결합 자 기반 접근 방식을 포함한 세 가지 기술 제품군이 포함됩니다. iai.uni-bonn.de/~jv/ssgip-bidirectional-final.pdf
sclv

그리고 방금 언급으로, 2008 년 반전에 대한 악한 해킹과 다방이 메시지가 있었다 put부여받은 기록 구조로 기능 Data: haskell.org/pipermail/haskell-cafe/2008-April/042193.html은 유사한 접근 방식을 사용하여 나중에 "무료"에서 (더 엄격하게, 더 일반적으로, 더 원칙적인 등) 발표했습니다.
sclv

그것은 2017 년이고 물론 논문에 대한 링크는 더 이상 유효하지 않습니다. 여기에 업데이트 된 것이 있습니다. pdfs.semanticscholar.org/5f0d/…
Mina Gabriel

37

아니요, 일반적으로 불가능합니다.

증명 : 유형의 bijective 함수 고려

type F = [Bit] -> [Bit]

data Bit = B0 | B1

다음 inv :: F -> F과 같은 인버터가 있다고 가정 합니다 inv f . f ≡ id. 우리가 함수를 테스트 한 말 f = id것을 확인하여,

inv f (repeat B0) -> (B0 : ls)

B0출력의 첫 번째 값 은 일정 시간이 지나야하므로이 결과를 얻기 위해 테스트 입력을 실제로 평가 n한 깊이와 inv호출 할 수있는 횟수 모두에 상한 이 있습니다 f. 이제 함수 제품군 정의

g j (B1 : B0 : ... (n+j times) ... B0 : ls)
   = B0 : ... (n+j times) ... B0 : B1 : ls
g j (B0 : ... (n+j times) ... B0 : B1 : ls)
   = B1 : B0 : ... (n+j times) ... B0 : ls
g j l = l

분명히, 모두에게있어서 0<j≤n, g j사실은 자기 반대입니다. 그래서 우리는 확인할 수 있어야합니다

inv (g j) (replicate (n+j) B0 ++ B1 : repeat B0) -> (B1 : ls)

그러나이 사항을 충족하기 위해, inv (g j)하나에 필요한 것

  • g j (B1 : repeat B0)깊이 평가n+j > n
  • head $ g j l적어도 n다른 목록 일치에 대해 평가replicate (n+j) B0 ++ B1 : ls

최대 그 시점에,의 적어도 하나 g j에서 구별 f하고 있기 때문에 inv f이러한 평가 중 하나 다하지 않았다, inv아마도 떨어져 말했다 수 없었다 -에서만 가능하다, 자신의 일부 런타임 측정을하는 짧은 IO Monad.

                                                                                                                                   ⬜


19

위키피디아에서 찾아 볼 수 있습니다 . 그것은 Reversible Computing 이라고 합니다.

일반적으로 당신은 그것을 할 수 없으며 기능적 언어에는 그 옵션이 없습니다. 예를 들면 :

f :: a -> Int
f _ = 1

이 함수에는 역이 없습니다.


1
f역이 있다고 말하는 것이 잘못된 것일까 요? 역이 비 결정적 함수라는 것뿐입니까?
Matt Fenwick

10
@MattFenwick 예를 들어 Haskell과 같은 언어에서 함수는 비 결정적이지 않습니다 (유형과 사용 방법을 변경하지 않고). 수학적으로 의 역을 설명 할 수 있더라도 g :: Int -> a의 역인 하스켈 함수 는 존재하지 않습니다 . ff
Ben

2
@Matt : 함수형 프로그래밍과 로직에서 "하단"을 찾습니다. "하단"은 모순되거나 종결되지 않거나 결정 불가능한 문제에 대한 해결책이기 때문에 "불가능한"값입니다 (이것은 단순한 모순 이상입니다. 설계를 탐색하는 동안 체계적으로 솔루션을 "추적"할 수 있습니다. 개발 중 "정의되지 않음"및 "오류"를 사용하는 공간). "하단"x의 유형은 a입니다. 그것은 모든 유형의 "거주"(또는 "가치")입니다. 유형이 명제이고 모든 명제를 만족시키는 가치가 없기 때문에 이것은 논리적 모순입니다. Haskell-Cafe에서 좋은 토론을 찾아보십시오
nomen

2
@Matt : 비결정론의 관점에서 역의 존재하지 않음을 특성화하기보다는 바닥의 관점에서 특성화해야합니다. f _ = 1의 역은 모든 유형에 있어야하기 때문에 바닥입니다 (또는 f는 단일 요소 이상을 가진 모든 유형에 대해 역함수를 갖지 않기 때문에 바닥입니다. 바닥이라는 것은 가치에 대한 주장으로 긍정적이고 부정적으로 받아 들일 수 있습니다. 임의 함수의 역을 "가치"최하위라고 현명하게 말할 수 있습니다. (정말 값은 아니지만)
nomen

1
훨씬 나중에 여기로 돌아와서 Matt가 무엇을 얻고 있는지 알 것 같습니다. 우리는 종종 목록을 통해 비결정론을 모델링하고 역에 대해서도 똑같이 할 수 있습니다. 의 역은 f x = 2 * xbe f' x = [x / 2], 그리고 역은 f _ = 1is f' 1 = [minBound ..]; f' _ = []입니다. 즉, 1에 대한 많은 역이 있고 다른 값에 대해서는 아무것도 없습니다.
amalloy

16

대부분의 기능적 언어가 아니라 논리 프로그래밍 또는 관계형 프로그래밍에서 정의하는 대부분의 함수는 실제로 함수가 아니라 "관계"이며 양방향으로 사용할 수 있습니다. 예를 들어 프롤로그 또는 칸렌을 참조하십시오.


1
또는 Mercury , 그렇지 않으면 Haskell의 정신을 많이 공유합니다. — 좋은 지적, +1.
leftaroundabout

11

이와 같은 작업은 거의 항상 결정할 수 없습니다. 일부 특정 기능에 대한 솔루션을 가질 수 있지만 일반적이지 않습니다.

여기에서는 어떤 함수에 역이 있는지조차 인식 할 수 없습니다. Barendregt, HP Lambda 미적분 : 구문 및 의미론을 인용 합니다. 노스 홀랜드, 암스테르담 (1984) :

람다 용어 집합은 비어 있거나 전체 집합이 아니면 중요하지 않습니다. A와 B가 (베타) 평등으로 닫힌 두 개의 사소하고 분리 된 람다 용어 집합이면 A와 B는 재귀 적으로 분리 할 수 ​​없습니다.

A를 가역 함수를 나타내는 람다 항의 집합으로, 나머지 B를 취합시다. 둘 다 비어 있지 않으며 베타 평등하에 닫힙니다. 따라서 함수가 반전 가능한지 여부를 결정할 수 없습니다.

(이것은 유형이 지정되지 않은 람다 미적분에 적용됩니다. TBH 우리가 반전하려는 함수의 유형을 알고있을 때 인수가 유형이 지정된 람다 미적분에 직접 적용될 수 있는지 여부는 알 수 없습니다.하지만 그럴 것이라고 확신합니다. 비슷한.)


11

함수의 영역을 열거 할 수 있고 범위의 요소가 같은지 비교할 수 있다면 다소 간단한 방식으로 할 수 있습니다. 열거한다는 것은 사용 가능한 모든 요소의 목록을 갖는 것을 의미합니다. 나는 Ocaml을 모르기 때문에 Haskell을 고수 할 것입니다 (또는 그것을 적절하게 대문자로 쓰는 방법까지도 ;-)

원하는 것은 도메인의 요소를 통해 실행하고 반전하려는 범위의 요소와 동일한 지 확인하고 작동하는 첫 번째 요소를 가져 오는 것입니다.

inv :: Eq b => [a] -> (a -> b) -> (b -> a)
inv domain f b = head [ a | a <- domain, f a == b ]

그것이 fbijection 이라고 말 했으므로 그러한 요소는 단 하나뿐입니다. 물론 비결은 도메인 열거가 실제로 유한 한 시간 내에 모든 요소 도달하도록하는 것 입니다. 당신이에서 전단 사 함수를 반전하려는 경우 IntegerInteger사용 [0,1 ..] ++ [-1,-2 ..]하지 않습니다 일을 당신은 부정적인 번호를 못할거야있다. 구체적으로, inv ([0,1 ..] ++ [-1,-2 ..]) (+1) (-3)가치를 산출하지 않습니다.

그러나 0 : concatMap (\x -> [x,-x]) [1..]다음 순서로 정수를 통해 실행되므로 작동 [0,1,-1,2,-2,3,-3, and so on]합니다. 참으로 inv (0 : concatMap (\x -> [x,-x]) [1..]) (+1) (-3)즉시 돌아옵니다 -4!

Control.Monad.Omega의 패키지는 당신이 좋은 방법 튜플 등등의 목록을 통해 실행 도움이 될 수 있습니다; 그런 패키지가 더 많이있을 거라고 확신합니다.하지만 모르겠습니다.


물론,이 접근 방식은 추악하고 비효율적 인 것은 말할 것도없고 다소 낮은 편이고 무차별 적입니다! 그래서 나는 당신의 질문의 마지막 부분, bijections를 '쓰기'하는 방법에 대한 몇 가지 언급으로 끝낼 것입니다. Haskell의 유형 시스템은 함수가 bijection이라는 것을 증명하는 것이 아닙니다. 당신은 정말로 Agda와 같은 것을 원합니다. 그러나 그것은 당신을 기꺼이 신뢰합니다.

(경고 : 테스트되지 않은 코드는 다음과 같습니다)

따라서 Bijection유형 a과 사이 에 s 의 데이터 유형을 정의 할 수 있습니다 b.

data Bi a b = Bi {
    apply :: a -> b,
    invert :: b -> a 
}

다음과 같이 원하는만큼의 상수 ( 'I know they 're bijections!' 라고 말할 수 있음 )와 함께 :

notBi :: Bi Bool Bool
notBi = Bi not not

add1Bi :: Bi Integer Integer
add1Bi = Bi (+1) (subtract 1)

다음과 같은 몇 가지 스마트 결합기 :

idBi :: Bi a a 
idBi = Bi id id

invertBi :: Bi a b -> Bi b a
invertBi (Bi a i) = (Bi i a)

composeBi :: Bi a b -> Bi b c -> Bi a c
composeBi (Bi a1 i1) (Bi a2 i2) = Bi (a2 . a1) (i1 . i2)

mapBi :: Bi a b -> Bi [a] [b]
mapBi (Bi a i) = Bi (map a) (map i)

bruteForceBi :: Eq b => [a] -> (a -> b) -> Bi a b
bruteForceBi domain f = Bi f (inv domain f)

나는 당신이 할 수 invert (mapBi add1Bi) [1,5,6]있고 얻을 수 있다고 생각합니다 [0,4,5]. 콤비 네이터를 현명하게 선택한다면 Bi손 으로 상수 를 작성해야하는 횟수 가 상당히 제한 될 수 있다고 생각합니다 .

결국, 함수가 bijection이라는 것을 안다면, Curry-Howard 동형이 프로그램으로 바뀔 수있는 그 사실에 대한 증명 스케치가 머릿속에 있기를 바랍니다. :-)


6

나는 최근에 이와 같은 문제를 다루고 있으며, 아니오, (a) 많은 경우 어렵지 않지만 (b) 전혀 효율적이지 않다고 말하고 싶습니다.

기본적으로, 당신은 가정 f :: a -> b, 그것은 f참으로 bjiection이다. f' :: b -> a정말 멍청한 방법으로 역 을 계산할 수 있습니다 .

import Data.List

-- | Class for types whose values are recursively enumerable.
class Enumerable a where
    -- | Produce the list of all values of type @a@.
    enumerate :: [a]

 -- | Note, this is only guaranteed to terminate if @f@ is a bijection!
invert :: (Enumerable a, Eq b) => (a -> b) -> b -> Maybe a
invert f b = find (\a -> f a == b) enumerate

경우 f전단 사 함수이며, enumerate진정으로 모든 값을 생성 a, 당신은 결국 공격 할 것이다 a그러한를 f a == b.

BoundedEnum인스턴스 가있는 유형은 간단하게 만들 수 있습니다 RecursivelyEnumerable. Enumerable유형의 쌍 도 만들 수 있습니다 Enumerable.

instance (Enumerable a, Enumerable b) => Enumerable (a, b) where
    enumerate = crossWith (,) enumerate enumerate

crossWith :: (a -> b -> c) -> [a] -> [b] -> [c]
crossWith f _ [] = []
crossWith f [] _ = []
crossWith f (x0:xs) (y0:ys) =
    f x0 y0 : interleave (map (f x0) ys) 
                         (interleave (map (flip f y0) xs)
                                     (crossWith f xs ys))

interleave :: [a] -> [a] -> [a]
interleave xs [] = xs
interleave [] ys = []
interleave (x:xs) ys = x : interleave ys xs

Enumerable유형의 분리도 마찬가지입니다 .

instance (Enumerable a, Enumerable b) => Enumerable (Either a b) where
    enumerate = enumerateEither enumerate enumerate

enumerateEither :: [a] -> [b] -> [Either a b]
enumerateEither [] ys = map Right ys
enumerateEither xs [] = map Left xs
enumerateEither (x:xs) (y:ys) = Left x : Right y : enumerateEither xs ys

사실 우리는 모두이 작업을 수행 할 수 (,)Either우리가 어떤 대수 데이터 형식을 위해 그것을 할 수 있다는 것을 아마 의미합니다.


5

모든 함수에 역이있는 것은 아닙니다. 토론을 일대일 기능으로 제한하는 경우 임의의 기능을 반전하는 기능은 모든 암호 시스템을 해독 할 수있는 능력을 부여합니다. 이론적으로도 이것이 가능하지 않기를 바랍니다!


13
모든 암호 시스템 (다른 이유로 실행 불가능한 일회성 패드와 같은 몇 가지 이상한 것 제외)은 무차별 대입에 의해 크랙 될 수 있습니다. 그것은 그것들을 덜 유용하게 만들지 않으며, 비현실적으로 비싼 반전 함수도 아닙니다.

정말인가요? 암호화 기능을 String encrypt(String key, String text)키없이 생각 하면 여전히 아무것도 할 수 없습니다. 편집 : 그리고 delnan이 말한 것.
mck

@MaciekAlbin 공격 모델에 따라 다릅니다. 예를 들어 선택한 일반 텍스트 공격은 키 추출을 허용 할 수 있으며, 그러면 해당 키로 암호화 된 다른 암호 텍스트를 공격 할 수 있습니다.

"가능하다"는 말은 합리적인 시간 안에 할 수있는 일을 의미했습니다. 나는 "계산 가능"을 의미하지 않았다 (나는 꽤 확신한다).
Jeffrey Scofield 2011

@JeffreyScofield 나는 당신의 요점을 봅니다. 그러나 저는 "이론상 실현 가능"이라는 점에 혼란스러워합니다. 실현 가능성은 실제로 수행하는 것이 얼마나 어려운지에 대해서만 언급하지 않습니까?

5

경우에 따라 bijective 함수를 기호 표현으로 변환하여 역함수를 찾을 수 있습니다. 이 예제를 기반으로 간단한 다항식 함수의 역을 찾기 위해이 Haskell 프로그램을 작성했습니다.

bijective_function x = x*2+1

main = do
    print $ bijective_function 3
    print $ inverse_function bijective_function (bijective_function 3)

data Expr = X | Const Double |
            Plus Expr Expr | Subtract Expr Expr | Mult Expr Expr | Div Expr Expr |
            Negate Expr | Inverse Expr |
            Exp Expr | Log Expr | Sin Expr | Atanh Expr | Sinh Expr | Acosh Expr | Cosh Expr | Tan Expr | Cos Expr |Asinh Expr|Atan Expr|Acos Expr|Asin Expr|Abs Expr|Signum Expr|Integer
       deriving (Show, Eq)

instance Num Expr where
    (+) = Plus
    (-) = Subtract
    (*) = Mult
    abs = Abs
    signum = Signum
    negate = Negate
    fromInteger a = Const $ fromIntegral a

instance Fractional Expr where
    recip = Inverse
    fromRational a = Const $ realToFrac a
    (/) = Div

instance Floating Expr where
    pi = Const pi
    exp = Exp
    log = Log
    sin = Sin
    atanh = Atanh
    sinh = Sinh
    cosh = Cosh
    acosh = Acosh
    cos = Cos
    tan = Tan
    asin = Asin
    acos = Acos
    atan = Atan
    asinh = Asinh

fromFunction f = f X

toFunction :: Expr -> (Double -> Double)
toFunction X = \x -> x
toFunction (Negate a) = \a -> (negate a)
toFunction (Const a) = const a
toFunction (Plus a b) = \x -> (toFunction a x) + (toFunction b x)
toFunction (Subtract a b) = \x -> (toFunction a x) - (toFunction b x)
toFunction (Mult a b) = \x -> (toFunction a x) * (toFunction b x)
toFunction (Div a b) = \x -> (toFunction a x) / (toFunction b x)


with_function func x = toFunction $ func $ fromFunction x

simplify X = X
simplify (Div (Const a) (Const b)) = Const (a/b)
simplify (Mult (Const a) (Const b)) | a == 0 || b == 0 = 0 | otherwise = Const (a*b)
simplify (Negate (Negate a)) = simplify a
simplify (Subtract a b) = simplify ( Plus (simplify a) (Negate (simplify b)) )
simplify (Div a b) | a == b = Const 1.0 | otherwise = simplify (Div (simplify a) (simplify b))
simplify (Mult a b) = simplify (Mult (simplify a) (simplify b))
simplify (Const a) = Const a
simplify (Plus (Const a) (Const b)) = Const (a+b)
simplify (Plus a (Const b)) = simplify (Plus (Const b) (simplify a))
simplify (Plus (Mult (Const a) X) (Mult (Const b) X)) = (simplify (Mult (Const (a+b)) X))
simplify (Plus (Const a) b) = simplify (Plus (simplify b) (Const a))
simplify (Plus X a) = simplify (Plus (Mult 1 X) (simplify a))
simplify (Plus a X) = simplify (Plus (Mult 1 X) (simplify a))
simplify (Plus a b) = (simplify (Plus (simplify a) (simplify b)))
simplify a = a

inverse X = X
inverse (Const a) = simplify (Const a)
inverse (Mult (Const a) (Const b)) = Const (a * b)
inverse (Mult (Const a) X) = (Div X (Const a))
inverse (Plus X (Const a)) = (Subtract X (Const a))
inverse (Negate x) = Negate (inverse x)
inverse a = inverse (simplify a)

inverse_function x = with_function inverse x

이 예제는 산술 표현식에서만 작동하지만 목록에서도 작동하도록 일반화 할 수 있습니다.


4

아니요, 모든 함수에 역이있는 것은 아닙니다. 예를 들어,이 함수의 역은 무엇입니까?

f x = 1

당신의 기능은 상수이고, 여기서 그것은 bijective 함수에 관한 것입니다.
Soleil-Mathieu Prévot
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.