9

ad-hoc 다형 함수와 파라 메트릭 다형 함수 사이를 변환하는 일반적인 방법이 있는지 궁금합니다. 다른 말로 표현하면, ad-hoc 다형 함수가 주어지면 매개 변수를 구현하는 방법은 무엇입니까? 그 반대의 경우는 어떨까요?ad-hoc 다형 함수와 파라 메트릭 다형 함수 사이를 변환하는 좋은 방법

예를 들어 sort을 취하십시오. 그것은 sortBy의 측면에서 sort :: Ord a => [a] -> [a] 쓰기 쉽다 :

sort :: Ord a => [a] -> [a] 
sort = sortBy compare 

하지만, 다른 방법은 주위에 내가 할 수있는 최선 조금 "객체 지향"이동하는 것입니다 지금까지, 까다로운 것 같다

import qualified Data.List as L 

data OrdVal a = OV (a -> a -> Ordering) a 

instance Eq (OrdVal a) where 
    (OV cmp a) == (OV _ b) = a `cmp` b == EQ 

instance Ord (OrdVal a) where 
    (OV cmp a) `compare` (OV _ b) = a `cmp` b 

sortBy :: (a -> a -> Ordering) -> [a] -> [a] 
sortBy cmp = map unOV . L.sort . map (OV cmp) 
    where 
    unOV (OV _ v) = v 

을 그러나 이것은 적절한 해결책보다 해킹처럼 들립니다.

그래서 내가 알고 싶습니다 :이 특정 예를 들어 더 나은 방법

  1. 이 있습니까?
  2. ad-hoc 다형 함수와 파라 메트릭 함수를 변환하는 일반적인 기술은 무엇입니까?
+1

사전을 전달할 수 있다면 (예 : Agda implicits와 같이) 사소한 일입니다. 그러나 일부 클래스/라이브러리는 일부 불변성을 보장하기 위해 사전을 전달할 수 없다는 사실을 악용합니다. 예를 들어 매번 다른 순서를 사용하여'Data.Set.insert'를 호출 할 수 있다고 상상해보십시오 ... – chi

+6

실제로 "해킹"은 실제로 작동하지만, 두 개의 별개의'cmp' 함수를' OrdVal a 값. 그렇게하면,'Ord' 인스턴스는'Ord' 법칙을 만족시키지 못합니다. – chi

답변

7

나는 반드시이 작업을 수행해야 말하는 게 아니에요,하지만 당신은 사용 reflection는리스트의 각 요소를 패키징 할 필요없이 비교 함수 주위에 통과 :

{-# LANGUAGE UndecidableInstances #-} 
import Data.Reflection 

newtype O a = O a 

instance Given (a -> a -> Ordering) => Eq (O a) where 
    x == y = compare x y == EQ 

instance Given (a -> a -> Ordering) => Ord (O a) where 
    compare (O x) (O y) = given x y 

다음과 같이 주어 () 위의 인프라, 당신은 다음 sort의 측면에서 sortBy을 작성할 수 있습니다

import Data.Coerce 
import Data.List (sort) 

sortBy :: (a -> a -> Ordering) -> [a] -> [a] 
sortBy cmp = give cmp $ from . sort . to 
    where 
    to :: [a] -> [O a] 
    to = coerce 

    from :: [O a] -> [a] 
    from = coerce 
,

+2

'Given'은 좀 악마 같으니까요. 팬텀을 newtype에 추가 한 다음'given'대신'Reifies'를 사용하고'give' 대신'reify'를 사용하고'given' 대신'reflect'를 사용하십시오. – dfeuer

4

선인장의 대답은 reflection에 다소 그늘 Given 클래스에 의존 (Data.Coerce를 사용하여, 우리는 O 래퍼의 모든 잠재적 인 런타임 비용을 피할 수 있습니다). 그러나 그것없이 반사를 사용하는 것도 가능합니다. 코어가 제조 검사

{-# LANGUAGE ScopedTypeVariables, MultiParamTypeClasses, UndecidableInstances #-} 

module SortReflection where 

import Data.Reflection 
import Data.List (sort) 
import Data.Proxy 
import Data.Coerce 

newtype O s a = O {getO :: a} 

instance Reifies s (a -> a -> Ordering) => Eq (O s a) where 
    a == b = compare a b == EQ 

instance Reifies s (a -> a -> Ordering) => Ord (O s a) where 
    compare = coerce (reflect (Proxy :: Proxy s)) 

sortBy :: forall a . (a -> a -> Ordering) -> [a] -> [a] 
sortBy cmp = reify cmp $ 
    \(_ :: Proxy s) -> coerce (sort :: [O s a] -> [O s a]) 

이것이 sortBy의 thin 래퍼로 컴파일 것을 나타낸다. Proxy 대신 Tagged을 기반으로 한 Reifies 클래스를 사용하면 더 얇아 보이지만 Ed Kmett는 생성하는 API를 좋아하지 않습니다.

+0

왜'sortBy' 구현에서'coerce'를 사용하지 않습니까? – Cirdec

+0

@Cirdec, 당신이 그것을 다른 곳에서 사용한다면 특별한 이유가 없습니다. 나는 그것이 다른 곳에서 유익 할 것이라는 것을 알았을 때 알지 못했다. 어쨌든, 나는 일반적으로'#를 사용하는 것을 선호한다.'와'. #'직접 적용하는 대신 적용 할 수 있습니다. 왜냐하면 그렇게하면 형식 서명의 수를 줄이는 경향이 있고 코드를 명확하게 할 수 있기 때문입니다. 'Data.Profunctor.Unsafe'를 사용할 수없는 경우에도'->'인스턴스의 비트는 쉽게 복사 될 수 있습니다. – dfeuer

+0

두 개의'강제 변환 '으로 필요한 유일한 서명은'sort :: [O s a] -> [O s a]'입니다. 'sortBy cmp = reify cmp $ \ (_ :: Proxy s) -> 강제로 정의하면. (sort :: [O s a] -> [O s a]). 강제로 '중간 목록의 잠재적 인 배정에 대해 걱정할 필요가 없습니다. – Cirdec