9

Tôi tự hỏi liệu có cách tổng quát để chuyển đổi giữa các hàm đa hình ad-hoc và các hàm đa hình tham số hay không. Nói cách khác, được đưa ra một chức năng đa hình ad-hoc, làm thế nào để thực hiện đối số tham số của nó? và còn cách nào khác?cách tốt để chuyển đổi giữa các hàm đa hình ad-hoc và các đa hình tham số

chụp sort làm ví dụ. thật dễ dàng để viết sort :: Ord a => [a] -> [a] về sortBy:

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

nhưng cách khác xung quanh dường như khó khăn, cho đến nay là tốt nhất tôi có thể làm là để đi một chút "hướng đối tượng":

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 

Nhưng điều này nghe có vẻ giống như một hack hơn là giải pháp thích hợp.

vì vậy tôi muốn biết:

  1. đang có những cách tốt hơn ví dụ cụ thể này?
  2. các kỹ thuật chung để chuyển đổi giữa các hàm đa hình ad-hoc và các hàm tham số là gì?
+1

Nếu chúng tôi có thể chuyển từ điển (ví dụ như trong Agda implicits), điều này sẽ không quan trọng. Tuy nhiên, tôi tin rằng một số lớp/thư viện khai thác thực tế là chúng tôi không thể chuyển từ điển để đảm bảo một số bất biến. Ví dụ, hãy tưởng tượng nếu chúng ta có thể gọi'Data.Set.insert' bằng cách đặt hàng khác nhau ... – chi

+6

Cũng lưu ý rằng "hack" của bạn hoạt động trong thực tế, nhưng chỉ khi bạn không bao giờ đóng gói hai hàm 'cmp' riêng biệt trong' OrdVal a' values. Nếu bạn làm thế, thì cá thể 'Ord' của bạn không thỏa mãn luật' Ord'. – chi

Trả lời

7

Tôi không nhất thiết phải nói rằng bạn nên làm điều này, nhưng bạn có thể sử dụng reflection để vượt qua xung quanh chức năng so sánh mà không cần phải gói nó lên với mỗi phần tử của danh sách:

{-# 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 

Given (heh) cơ sở hạ tầng trên, sau đó bạn có thể viết sortBy về sort như sau:

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 

(lưu ý rằng bằng cách sử dụng Data.Coerce, chúng tôi tránh tất cả các chi phí thời gian chạy tiềm năng của O wrapper)

+2

'Given' là một tà ác, và bạn thực sự không cần nó ở đây. Thêm một ma vào newtype và sau đó sử dụng 'Reifies' thay vì' Given', 'reify' thay vì' give', và 'reflect' thay vì' given'. – dfeuer

4

Cactus của câu trả lời dựa trên lớp hơi râm Given trong reflection. Tuy nhiên, có thể sử dụng sự phản chiếu mà không có điều đó.

{-# 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]) 

Kiểm tra lõi được sản xuất cho biết rằng trình biên dịch này bao bọc mỏng khoảng sortBy. Nó trông mỏng hơn khi sử dụng lớp Reifies dựa trên Tagged thay vì Proxy, nhưng Ed Kmett không thích API sản xuất.

+0

Tại sao không sử dụng 'coerce' trong triển khai' sortBy'? – Cirdec

+0

@Cirdec, không có lý do cụ thể nào để không, nếu bạn đang sử dụng nó ở nơi khác. Tôi đã không nhận ra khi tôi bắt đầu ra rằng nó sẽ có lợi ở nơi khác. Trong mọi trường hợp, tôi thường thích sử dụng '#.'và'. # 'khi áp dụng thay vì sử dụng' coerce' trực tiếp, vì làm như vậy có xu hướng giảm số chữ ký kiểu cần thiết và có thể làm cho mã rõ ràng hơn. Ngay cả khi 'Data.Profunctor.Unsafe' không có sẵn, các bit của thể hiện' -> 'có thể được sao chép dễ dàng. – dfeuer

+0

Chữ ký kiểu duy nhất bạn cần với hai 'coerce' là' sắp xếp :: [O s a] -> [O s a] '. Nếu bạn định nghĩa 'sortBy cmp = reify cmp $ \ (_ :: Proxy s) -> ép buộc. (sắp xếp :: [O s a] -> [O s a]). coerce' bạn không phải lo lắng về việc phân bổ danh sách trung gian tiềm năng. – Cirdec

Các vấn đề liên quan