Tại sao không có một kiểu chữ cho các chức năng?


17

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 ...

  1. 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.

  2. 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 (>>=)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, returnhoặc arrvới nhiều loại của tôi, vì vậy tôi không thể sử dụng Applicative, Monadhoặ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ì Categorykhô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 đó.


2
Gọi tôi là điên, nhưng khi tôi nghĩ về một kiểu chữ như bạn đang mô tả đó là để áp dụng và sáng tác, v.v ... tôi nghĩ về các hàm xử lý ứng dụng, đó là các chức năng. Có lẽ đó là loại lớp bạn đang nghĩ đến?
Jimmy Hoffa

1
Tôi không nghĩ Applicativelà 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ở đó abchỉ đị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.
Steve314

3
nếu bạn muốn nghịch đảo thì điều đó có nghĩa là một nhóm?
jk.

1
@jk. điểm tuyệt vời, nhận ra rằng có rất nhiều điều để đọc về nghịch đảo các chức năng có thể khiến OP tìm thấy những gì anh ta đang tìm kiếm. Đây là một số thú vị đọc về chủ đề này. Nhưng một google cho hàm haskell nghịch đảo cung cấp nhiều nội dung đọc tò mò. Có lẽ anh ấy chỉ muốn Data. Group
Jimmy Hoffa

2
@ Steve314 Tôi nghĩ các chức năng với thành phần là một thể loại đơn. Chúng là một monoid nếu tên miền và tên miền luôn giống nhau.
Tim Seguine

Câu trả lời:


14

Chà, tôi không biết bất kỳ ý tưởng nào được quảng cáo là đại diện cho những thứ "chức năng-y". Nhưng có một số đến gần

Thể loại

Nếu bạn có một khái niệm chức năng đơn giản có danh tính và thành phần, hơn bạn có một thể loại.

class Category c where
  id :: c a a
  (.) :: c b c -> c a b -> c a c

Điểm bất lợi là bạn không thể tạo ra một loại dụ tốt đẹp với một tập các đối tượng ( a, bc). Bạn có thể tạo một lớp thể loại tùy chỉnh tôi giả sử.

Mũi tên

Nếu các chức năng của bạn có khái niệm về sản phẩm và có thể tiêm các chức năng tùy ý, thì mũi tên là dành cho bạn

 class Arrow a where
   arr :: (b -> c) -> a b c
   first :: a b c -> a (b, d) (c, d)
   second :: a b c -> a (d, b) (d, c)

ArrowApply có một khái niệm về ứng dụng có vẻ quan trọng cho những gì bạn muốn.

Người nộp đơn

Ứng viên có khái niệm về ứng dụng của bạn, tôi đã sử dụng chúng trong AST để thể hiện ứng dụng chức năng.

class Functor f => Applicative f where
  pure :: a -> f a
  (<*>) :: f (a -> b) -> f b -> f c

Có nhiều ý tưởng khác. Nhưng một chủ đề phổ biến là xây dựng một số cấu trúc dữ liệu đại diện cho chức năng của bạn và hơn là chuyển nó sang một chức năng diễn giải.

Đây cũng là bao nhiêu đơn vị miễn phí làm việc. Tôi khuyên bạn nên chọc vào những thứ này nếu bạn cảm thấy dũng cảm, chúng là một công cụ mạnh mẽ cho những thứ bạn đang đề xuất và về cơ bản cho phép bạn xây dựng cơ sở hạ tầng bằng cách sử dụng doký hiệu và sau đó thu gọn nó thành một tính toán phụ với các chức năng khác nhau . Nhưng điều tuyệt vời là các chức năng này chỉ hoạt động trên cơ sở hạ tầng và không thực sự nhận thức được cách bạn tạo ra tất cả. Đây là những gì tôi muốn đề xuất cho ví dụ về phiên dịch viên.


Thể loại dường như thiếu ứng dụng - ($). Mũi tên trông giống như quá mức cần thiết ngay từ cái nhìn đầu tiên, nhưng vẫn ArrowApplycó vẻ đầy hứa hẹn - miễn là tôi không cần cung cấp bất cứ điều gì tôi không thể làm được. +1 cho hiện tại, với nhiều kiểm tra để làm.
Steve314

3
@ Steve314 Danh mục không có ứng dụng, nhưng các đơn vị thiếu một cách phổ biến để chạy chúng, không có nghĩa là chúng không hữu ích
Daniel Gratzer

Có một lý do phổ biến khiến tôi không thể sử dụng Applicativehoặc Arrow(hoặc Monad) - Tôi không thể bao hàm một hàm bình thường (nói chung) vì các giá trị loại của tôi đại diện cho một hàm nhưng được biểu thị bằng dữ liệu và sẽ không hỗ trợ các hàm tùy ý nếu có một cách để dịch Điều đó có nghĩa là tôi không thể cung cấp pure, arrhoặc returnví dụ. BTW - những lớp này rất hữu ích nhưng tôi không thể sử dụng chúng cho mục đích cụ thể này. Arrowkhông phải là "quá mức cần thiết" - đó là một ấn tượng sai lầm từ lần cuối cùng tôi cố đọc bài báo, khi tôi chưa sẵn sàng để hiểu nó.
Steve314

@ Steve314 Ý tưởng cung cấp giao diện đơn nguyên để xây dựng dữ liệu là những gì các đơn vị miễn phí được sử dụng cho, hãy kiểm tra chúng
Daniel Gratzer

Tôi đã xem video từ Haskell Exchange 2013 - Andres Löh chắc chắn giải thích rõ về nó, mặc dù tôi vẫn có thể cần xem lại, chơi với kỹ thuật, v.v. Mục tiêu của tôi là có sự trừu tượng hóa của một hàm bằng cách sử dụng một biểu diễn không phải là một hàm (nhưng có chức năng thông dịch). Tôi không cần một sự trừu tượng hóa tác dụng phụ và tôi không cần một ký hiệu rõ ràng cho các hoạt động tuần tự. Khi chức năng trừu tượng hóa này được sử dụng, các ứng dụng và tác phẩm sẽ được thực hiện một lần trong một thuật toán trong thư viện khác.
Steve314

2

Như bạn chỉ ra, vấn đề chính khi sử dụng Applicative ở đây là không có định nghĩa hợp lý cho pure. Do đó, Applyđã được phát minh. Ít nhất, đó là sự hiểu biết của tôi về nó.

Thật không may, tôi cũng không có ví dụ về các trường hợp Applyđó Applicative. Nó được tuyên bố là đúng với IntMap, nhưng tôi không biết tại sao. Tương tự, tôi không biết liệu ví dụ của bạn - số nguyên bù - có thừa nhận một Applythể hiện hay không.


Điều này đọc giống như một bình luận, xem Cách trả lời
gnat

Lấy làm tiếc. Đây là câu trả lời đầu tiên của tôi ít nhiều
dùng185657

Làm thế nào để bạn đề nghị tôi cải thiện câu trả lời?
dùng185657

xem xét chỉnh sửa ing để giúp người đọc thấy câu trả lời của bạn giải quyết câu hỏi như thế nào, "tại sao không có kiểu chữ cho các chức năng? Có phải" chỉ vì không có "hoặc" vì nó không hữu ích như bạn nghĩ "? Có một vấn đề cơ bản với ý tưởng? "
gnat

1
Tôi hy vọng điều này tốt hơn
user185657 01/07/2015

1

Ngoài việc đề cập Category, ArrowApplicative:

Tôi cũng đã được phát hiện Data.Lambdabởi Conal Elliott:

Một số lớp giống như hàm, có cấu trúc giống lambda

Có vẻ thú vị, tất nhiên, nhưng khó hiểu nếu không có ví dụ ...

Ví dụ

Có thể tìm thấy các ví dụ trong trang wiki về các giá trị hữu hình (TV) dường như là một trong những điều gây ra việc tạo ra TypeComposethư viện; xem Đầu vào và đầu ra có giá trị chức năng .

Ý tưởng của thư viện TV là hiển thị các giá trị Haskell (bao gồm các chức năng) một cách hữu hình.

Để tuân theo quy tắc StackOverflow về việc không đăng các bài hát đơn giản, tôi sao chép một số bit bên dưới để đưa ra ý tưởng về những điều sau:

Ví dụ đầu tiên ghi:

apples, bananas :: CInput Int
apples  = iTitle "apples"  defaultIn
bananas = iTitle "bananas" defaultIn

shoppingO :: COutput (Int -> Int -> Int)
shoppingO = oTitle "shopping list" $
            oLambda apples (oLambda bananas total)

shopping :: CTV (Int -> Int -> Int)
shopping = tv shoppingO (+)

cung cấp khi chạy dưới dạng runIO shopping(xem ở đó để biết thêm nhận xét, GUI và nhiều ví dụ khác):

shopping list: apples: 8
bananas: 5
total: 13

Làm thế nào để giải quyết câu hỏi này? xem Cách trả lời
gnat

@gnat Tôi nghĩ rằng các định nghĩa trong Data.Lambdacung cấp các lớp cho những thứ giống như chức năng (được yêu cầu) ... Tôi không chắc chắn những thứ này sẽ được sử dụng như thế nào. Tôi đã khám phá điều này một chút. Có lẽ, họ không cung cấp một sự trừu tượng cho ứng dụng chức năng mặc dù.
imz - Ivan Zakharyaschev
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.