Trong một vấn đề học tập mà tôi đã gặp phải, tôi nhận ra rằng tôi cần một kiểu chữ cho các chức năng với các thao tác để áp dụng, soạn thảo, v.v ... Lý do ...
Có thể thuận tiện để xử lý một biểu diễn của hàm như thể nó là chính hàm đó, do đó, việc áp dụng hàm này hoàn toàn sử dụng trình thông dịch và việc soạn thảo các hàm tạo ra một mô tả mới.
Khi bạn có một kiểu chữ cho các hàm, bạn có thể có các kiểu chữ xuất phát cho các loại hàm đặc biệt - trong trường hợp của tôi, tôi muốn các hàm không thể đảo ngược.
Ví dụ, các hàm áp dụng số nguyên bù có thể được biểu thị bằng một ADT có chứa một số nguyên. Áp dụng các hàm đó chỉ có nghĩa là thêm số nguyên. Thành phần được thực hiện bằng cách thêm các số nguyên được bọc. Hàm nghịch đảo có số nguyên bị phủ định. Hàm nhận dạng kết thúc bằng không. Hàm hằng không thể được cung cấp vì không có đại diện phù hợp cho nó.
Tất nhiên, nó không cần đánh vần mọi thứ như thể các giá trị là các hàm Haskell chính hãng, nhưng một khi tôi có ý tưởng, tôi nghĩ rằng một thư viện như thế phải tồn tại và thậm chí có thể sử dụng các cách viết chuẩn. Nhưng tôi không thể tìm thấy một kiểu chữ như vậy trong thư viện Haskell.
Tôi đã tìm thấy mô-đun Data.Function , nhưng không có kiểu chữ - chỉ một số chức năng phổ biến cũng có sẵn từ Prelude.
Vậy - tại sao không có một kiểu chữ cho các chức năng? Có phải "chỉ vì không có" hay "bởi vì nó không hữu ích như bạn nghĩ"? Hoặc có thể có một vấn đề cơ bản với ý tưởng?
Vấn đề lớn nhất có thể xảy ra cho đến nay là ứng dụng chức năng trên các chức năng thực tế có thể phải được trình biên dịch xử lý đặc biệt để tránh sự cố lặp - để áp dụng chức năng này, tôi cần áp dụng chức năng ứng dụng chức năng, và để làm điều đó tôi cần gọi hàm ứng dụng chức năng, và để làm điều đó ...
Thêm manh mối
Mã ví dụ để hiển thị những gì tôi đang nhắm đến ...
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-}
-- In my first version, Doable only had the one argument f. This version
-- seemed to be needed to support the UndoableOffset type.
--
-- It seems to work, but it also seems strange. In particular,
-- the composition function - a and b are in the class, but c isn't,
-- yet there's nothing special about c compared with a and b.
class Doable f a b where
fwdApply :: f a b -> a -> b
compDoable :: f b c -> f a b -> f a c
-- In the first version, I only needed a constraint for
-- Doable f a b, but either version makes sense.
class (Doable f a b, Doable f b a) => Undoable f a b where
bwd :: f a b -> f b a
bwdApply :: f a b -> b -> a
bwdApply f b = fwdApply (bwd f) b
-- Original ADT - just making sure I could wrap a pair of functions
-- and there were no really daft mistakes.
data UndoableFn a b = UFN { getFwd :: a -> b, getBwd :: b -> a }
instance Doable UndoableFn a b where
fwdApply = getFwd
compDoable f g = UFN ((getFwd f) . (getFwd g)) ((getBwd g) . (getBwd f))
instance Undoable UndoableFn a b where
bwd f = UFN (getBwd f) (getFwd f)
bwdApply = getBwd
-- Making this one work led to all the extensions. This representation
-- can only represent certain functions. I seem to need the typeclass
-- arguments, but also to need to restrict which cases can happen, hence
-- the GADT. A GADT with only one constructor still seems odd. Perhaps
-- surprisingly, this type isn't just a toy (except that the whole thing's
-- a toy really) - it's one real case I need for the exercise. Still a
-- simple special case though.
data UndoableOffset a b where
UOFF :: Int -> UndoableOffset Int Int
instance Doable UndoableOffset Int Int where
fwdApply (UOFF x) y = y+x
compDoable (UOFF x) (UOFF y) = UOFF (x+y)
instance Undoable UndoableOffset Int Int where
bwdApply (UOFF x) y = y-x
bwd (UOFF x) = UOFF (-x)
-- Some value-constructing functions
-- (-x) isn't shorthand for subtraction - whoops.
undoableAdd :: Int -> UndoableFn Int Int
undoableAdd x = UFN (+x) (\y -> y-x)
undoableMul :: Int -> UndoableFn Int Int
undoableMul x = UFN (*x) (`div` x)
-- With UndoableFn, it's possible to define an invertible function
-- that isn't invertible - to break the laws. To prevent that, need
-- the UFN constructor to be private (and all public ops to preserve
-- the laws). undoableMul is already not always invertible.
validate :: Undoable f a b => Eq a => f a b -> a -> Bool
validate f x = (bwdApply f (fwdApply f x)) == x
-- Validating a multiply-by-zero invertible function shows the flaw
-- in the validate-function plan. Must try harder.
main = do putStrLn . show $ validate (undoableAdd 3) 5
putStrLn . show $ validate (undoableMul 3) 5
--putStrLn . show $ validate (undoableMul 0) 5
fb1 <- return $ UOFF 5
fb2 <- return $ UOFF 7
fb3 <- return $ compDoable fb1 fb2
putStrLn $ "fwdApply fb1 3 = " ++ (show $ fwdApply fb1 3)
putStrLn $ "bwdApply fb1 8 = " ++ (show $ bwdApply fb1 8)
putStrLn $ "fwdApply fb3 2 = " ++ (show $ fwdApply fb3 2)
putStrLn $ "bwdApply fb3 14 = " ++ (show $ bwdApply fb3 14)
Ứng dụng này bao gồm một loại thống nhất trong đó các giá trị hợp nhất không bằng nhau, nhưng có liên quan thông qua các hàm khả nghịch này - logic kiểu Prolog nhưng có a = f(b)
ràng buộc hơn là a = b
. Hầu hết các thành phần sẽ là kết quả của việc tối ưu hóa cấu trúc tìm kết hợp. Sự cần thiết phải đảo ngược nên rõ ràng.
Nếu không có mục nào trong tập hợp nhất có giá trị chính xác, thì một mục cụ thể chỉ có thể được định lượng tương đối so với mục khác trong tập hợp nhất đó. Đó là lý do tại sao tôi không muốn sử dụng các hàm "thực" - tính toán các giá trị tương đối đó. Tôi có thể bỏ toàn bộ khía cạnh chức năng và chỉ có số lượng tuyệt đối và tương đối - tôi có lẽ chỉ cần số / vectơ và (+)
- nhưng phi hành gia kiến trúc bên trong của tôi muốn niềm vui của anh ấy.
Cách duy nhất để tôi phá vỡ các liên kết một lần nữa là thông qua quay lui và mọi thứ đều thuần túy - union-find sẽ được thực hiện bằng cách sử dụng các khóa thành một IntMap
"con trỏ". Tôi có công việc tìm kiếm kết hợp đơn giản, nhưng khi tôi chưa thêm các chức năng khả nghịch, không có điểm nào liệt kê nó ở đây.
Những lý do tôi không thể sử dụng Applicative, Monad, Arrow, v.v.
Các hoạt động chính tôi cần lớp trừu tượng hóa chức năng để cung cấp là ứng dụng và thành phần. Nghe có vẻ quen thuộc - ví dụ Applicative
(<*>)
, Monad
(>>=)
và Arrow
(>>>)
là tất cả các chức năng sáng tác. Tuy nhiên, các kiểu thực hiện trừu tượng hàm trong trường hợp của tôi sẽ chứa một số cấu trúc dữ liệu đại diện cho một hàm, nhưng không (và không thể chứa) một hàm và chỉ có thể biểu thị một số hàm hạn chế.
Như tôi đã đề cập trong phần giải thích về mã, đôi khi tôi chỉ có thể định lượng một mục tương đối với một mục khác vì không có mục nào trong cụm "hợp nhất" có giá trị chính xác. Tôi muốn có thể rút ra một biểu diễn của hàm đó, nói chung sẽ là thành phần của một số hàm được cung cấp (đi lên một tổ tiên chung trong liên kết / tìm cây) và của một số hàm nghịch đảo (đi ngược xuống hàm kia mục).
Trường hợp đơn giản - trong đó các "hàm" ban đầu được giới hạn ở các "hàm" bù nguyên, tôi muốn kết quả tổng hợp dưới dạng "hàm" bù nguyên - thêm các phần bù thành phần. Đó là một phần lớn lý do tại sao chức năng sáng tác cần phải có trong lớp cũng như chức năng ứng dụng.
Điều này có nghĩa tôi không thể cung cấp các hoạt động pure
, return
hoặc arr
với nhiều loại của tôi, vì vậy tôi không thể sử dụng Applicative
, Monad
hoặc Arrow
.
Đây không phải là một thất bại của những loại đó - nó không phù hợp với sự trừu tượng. Sự trừu tượng mà tôi muốn là của một hàm thuần túy đơn giản. Chẳng hạn, không có tác dụng phụ, và không cần xây dựng một ký hiệu thuận tiện để sắp xếp và soạn thảo các hàm khác với một hàm tương đương với tiêu chuẩn (.) Áp dụng cho tất cả các hàm.
Tôi có thể ví dụ Category
. Tôi tự tin rằng tất cả những thứ hữu dụng của tôi sẽ có thể cung cấp danh tính, mặc dù tôi có lẽ không cần nó. Nhưng vì Category
không hỗ trợ ứng dụng, dù sao tôi vẫn cần một lớp dẫn xuất để thêm thao tác đó.
Applicative
là hoàn toàn đúng - nó yêu cầu các giá trị được bọc cũng như các hàm, trong khi tôi chỉ muốn bọc các hàm và các hàm được bọc thực sự là các hàm, trong khi các hàm được bọc của tôi thường không có (trong trường hợp chung nhất, chúng là các hàm mô tả AST). Trường hợp <*>
có kiểu f (a -> b) -> f a -> f b
, tôi muốn một toán tử ứng dụng có kiểu g a b -> a -> b
ở đó a
và b
chỉ định miền và tên miền của hàm được gói, nhưng những gì bên trong trình bao bọc không (nhất thiết) là một hàm thực. Trên mũi tên - có thể, tôi sẽ có một cái nhìn.