ทำไมไม่มีประเภทของฟังก์ชัน?


17

ในปัญหาการเรียนรู้ที่ฉันทำไปด้วยฉันรู้ว่าฉันต้องการประเภทของฟังก์ชันที่มีการใช้งานการเขียนเป็นต้นเหตุผล ...

  1. มันจะสะดวกในการปฏิบัติต่อการเป็นตัวแทนของฟังก์ชั่นราวกับว่ามันเป็นฟังก์ชั่นของตัวเองเพื่อให้การใช้ฟังก์ชั่นโดยนัยใช้ล่ามและฟังก์ชั่นการแต่งได้คำอธิบายใหม่

  2. เมื่อคุณมี typeclass สำหรับฟังก์ชั่นคุณสามารถได้รับ typeclass สำหรับฟังก์ชั่นพิเศษ - ในกรณีของฉันฉันต้องการฟังก์ชั่นกลับด้าน

ตัวอย่างเช่นฟังก์ชั่นที่ใช้การชดเชยจำนวนเต็มอาจแสดงโดย ADT ที่มีจำนวนเต็ม การใช้ฟังก์ชั่นเหล่านั้นหมายถึงการเพิ่มจำนวนเต็ม องค์ประกอบถูกนำมาใช้โดยการเพิ่มจำนวนเต็มห่อ ฟังก์ชันผกผันมีจำนวนเต็มค่าเป็นลบ ฟังก์ชั่นเอกลักษณ์ห่อศูนย์ ไม่สามารถจัดหาฟังก์ชั่นคงที่ได้เนื่องจากไม่มีตัวแทนที่เหมาะสมสำหรับมัน

แน่นอนว่ามันไม่จำเป็นต้องสะกดสิ่งต่าง ๆ ราวกับว่ามันเป็นค่าของฟังก์ชั่น Haskell ของแท้ แต่เมื่อฉันมีความคิดฉันคิดว่าห้องสมุดแบบนั้นต้องมีอยู่แล้วและอาจใช้การสะกดมาตรฐาน แต่ฉันไม่สามารถหาประเภทของงานพิมพ์ในห้องสมุด Haskell ได้

ฉันพบโมดูลData.Functionแต่ไม่มีประเภทของงานพิมพ์ - มีเพียงฟังก์ชันทั่วไปบางส่วนที่มีให้จาก Prelude

ดังนั้น - ทำไมไม่มี typeclass สำหรับฟังก์ชั่น? เป็น "เพียงเพราะไม่มี" หรือ "เพราะไม่มีประโยชน์อย่างที่คุณคิด" หรืออาจมีปัญหาพื้นฐานกับความคิด?

ปัญหาที่เป็นไปได้ที่ใหญ่ที่สุดที่ฉันเคยคิดมาก่อนก็คือฟังก์ชั่นแอปพลิเคชั่นในฟังก์ชั่นที่เกิดขึ้นจริงอาจจะต้องได้รับการใส่รหัสเป็นพิเศษโดยคอมไพเลอร์เพื่อหลีกเลี่ยงปัญหาการวนซ้ำ และเพื่อที่ฉันจะต้องเรียกใช้ฟังก์ชั่นแอปพลิเคชั่นและเพื่อที่จะทำ ...

เบาะแสเพิ่มเติม

โค้ดตัวอย่างเพื่อแสดงสิ่งที่ฉันตั้งใจจะ ...

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

โปรแกรมประยุกต์ที่เกี่ยวข้องกับชนิดของการรวมกันที่ค่าแบบครบวงจรจะไม่เท่ากัน แต่มีความสัมพันธ์ผ่านฟังก์ชั่นผกผันเหล่านั้น - ตรรกะเปิดฉากสไตล์ แต่มีa = f(b)ข้อ จำกัด a = bมากกว่า องค์ประกอบส่วนใหญ่จะเป็นผลมาจากการปรับโครงสร้างการค้นหาแบบรวม ความจำเป็นในการผกผันควรชัดเจน

หากไม่มีรายการในชุดรวมมีค่าที่แน่นอนแล้วรายการเฉพาะสามารถวัดปริมาณได้เมื่อเทียบกับรายการอื่นในชุดรวม นั่นเป็นเหตุผลที่ฉันไม่ต้องการใช้ฟังก์ชั่น "ของจริง" - คำนวณค่าสัมพัทธ์เหล่านั้น ฉันสามารถวางฟังก์ชั่นทั้งหมดและมีปริมาณสัมบูรณ์และสัมพัทธ์ - ฉันอาจต้องการเพียงตัวเลข / เวกเตอร์และ(+)- แต่นักบินอวกาศสถาปัตยกรรมภายในของฉันต้องการความสนุกของเขา

วิธีเดียวที่ฉันจะแยกลิงค์ออกจากกันอีกครั้งก็คือการย้อนรอยและทุกอย่างบริสุทธิ์ - สหภาพ - การค้นหาจะทำโดยใช้กุญแจIntMapเป็น "ตัวชี้" ฉันมีสหภาพหางานง่าย ๆ แต่เมื่อฉันยังไม่ได้เพิ่มฟังก์ชั่นที่กลับด้านได้ไม่มีจุดที่แสดงไว้ที่นี่

เหตุผลที่ฉันไม่สามารถใช้ Applicative, Monad, Arrow เป็นต้น

การดำเนินการหลักที่ฉันต้องการคลาส abstraction ของฟังก์ชันเพื่อจัดเตรียมคือแอ็พพลิเคชันและองค์ประกอบ ว่าเสียงที่คุ้นเคย - เช่นApplicative (<*>), Monad (>>=)และArrow (>>>)มีฟังก์ชั่นทุกองค์ประกอบ อย่างไรก็ตามประเภทที่ใช้ฟังก์ชั่นนามธรรมในกรณีของฉันจะมีโครงสร้างข้อมูลบางอย่างที่แสดงถึงฟังก์ชั่น แต่ที่ไม่ได้ (และไม่สามารถมี) ฟังก์ชั่นและที่สามารถเป็นตัวแทนของชุด จำกัด ฟังก์ชั่นบางอย่าง

ดังที่ฉันได้กล่าวถึงในคำอธิบายของรหัสบางครั้งฉันสามารถหาจำนวนไอเท็มที่สัมพันธ์กับรายการอื่นเท่านั้นเนื่องจากไม่มีรายการในคลัสเตอร์ "รวม" ที่มีค่าที่แน่นอน ฉันต้องการที่จะได้รับการเป็นตัวแทนของฟังก์ชั่นนั้นซึ่งโดยทั่วไปจะเป็นองค์ประกอบของฟังก์ชั่นที่มีให้หลาย (เดินขึ้นไปที่บรรพบุรุษร่วมกันในยูเนี่ยน / หาต้นไม้) และฟังก์ชั่นอินเวอร์ส สิ่งของ).

กรณีง่าย - ที่ "ฟังก์ชั่น" ดั้งเดิมถูก จำกัด เพียงจำนวนเต็ม "ฟังก์ชั่น" ฉันต้องการผลประกอบการเป็นฟังก์ชั่น "จำนวนเต็มชดเชย" - เพิ่มองค์ประกอบ offsets นั่นเป็นส่วนสำคัญที่ว่าทำไมฟังก์ชั่นการแต่งเพลงจึงต้องอยู่ในคลาสเช่นเดียวกับแอปพลิเคชันฟังก์ชั่น

ซึ่งหมายความว่าฉันไม่สามารถให้การดำเนินงานpure, returnหรือarrประเภทของฉันดังนั้นฉันไม่สามารถใช้Applicative, หรือMonadArrow

นี่ไม่ใช่ความล้มเหลวของประเภทเหล่านี้ - มันเป็นสิ่งที่ไม่เข้ากับแนวนามธรรม สิ่งที่เป็นนามธรรมที่ฉันต้องการนั้นเป็นฟังก์ชั่นบริสุทธิ์ที่เรียบง่าย ไม่มีผลข้างเคียงตัวอย่างเช่นและไม่จำเป็นต้องสร้างสัญกรณ์ที่สะดวกสำหรับการจัดลำดับและการเขียนฟังก์ชั่นอื่นที่ไม่ใช่เทียบเท่าของมาตรฐาน (.) ที่ใช้กับทุกฟังก์ชั่น

ฉันจะทำได้Categoryเช่น ฉันมั่นใจว่าทุกฟังก์ชั่นการใช้งานของฉันจะสามารถระบุตัวตนได้แม้ว่าฉันอาจไม่ต้องการมันก็ตาม แต่เนื่องจากCategoryไม่รองรับแอปพลิเคชันฉันยังต้องการคลาสที่ได้รับมาเพื่อเพิ่มการดำเนินการนั้น


2
โทรหาฉันบ้า แต่เมื่อฉันนึกถึง typeclass เหมือนที่คุณกำลังอธิบายนั่นคือการใช้และการแต่ง ฯลฯ ฉันคิดเกี่ยวกับฟังก์ชั่น applicative ซึ่งเป็นฟังก์ชั่น บางทีนั่นเป็นคลาสประเภทที่คุณคิดอยู่ใช่ไหม
จิมมี่ฮอฟฟา

1
ฉันไม่คิดว่าApplicativeมันค่อนข้างถูกต้อง - มันต้องการค่าที่จะห่อรวมทั้งฟังก์ชั่นในขณะที่ฉันเพียงต้องการห่อฟังก์ชั่นและฟังก์ชั่นห่อเป็นจริงฟังก์ชั่นในขณะที่ฟังก์ชั่นห่อของฉันจะไม่ กรณีทั่วไปส่วนใหญ่เป็น AST ที่อธิบายฟังก์ชัน) <*>มีประเภทอยู่ที่ไหนf (a -> b) -> f a -> f bฉันต้องการผู้ให้บริการแอปพลิเคชั่นที่มีประเภทg a b -> a -> bที่ไหนaและbระบุโดเมนและโคโดเมนของฟังก์ชั่นที่ถูกห่อไว้ แต่สิ่งที่อยู่ภายใน wrapper ไม่ได้เป็นฟังก์ชันจริง (จำเป็น) On Arrows - อาจเป็นไปได้ที่ฉันจะดู
Steve314

3
ถ้าคุณต้องการผกผันจะไม่หมายความว่ากลุ่ม?
jk

1
@jk จุดที่ดีโดยตระหนักว่ามีหลายสิ่งที่ต้องอ่านเกี่ยวกับฟังก์ชันผกผันซึ่งอาจนำไปสู่ ​​OP เพื่อค้นหาสิ่งที่เขากำลังมองหา นี่คือบางส่วนของการอ่านออกที่น่าสนใจในหัวข้อ แต่การใช้ฟังก์ชั่น google for haskell กลับมีเนื้อหาการอ่านที่น่าสนใจมากมาย บางทีเขาแค่ต้องการ Data.Group
Jimmy Hoffa

2
@ Steve314 ฉันคิดว่าฟังก์ชั่นที่มีองค์ประกอบเป็นหมวดหมู่ monoidal พวกเขาเป็น monoid ถ้าโดเมนและ codomain เหมือนกันเสมอ
Tim Seguine

คำตอบ:


14

ฉันไม่ทราบว่ามีแนวคิดใดบ้างที่ทำตลาดเองว่าเป็นตัวแทนของ "ฟังก์ชัน -y" แต่มีหลายอย่างที่เข้ามาใกล้

หมวดหมู่

หากคุณมีแนวคิดฟังก์ชั่นที่เรียบง่ายที่มีตัวตนและองค์ประกอบกว่าที่คุณมีหมวดหมู่

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

ข้อเสียคือคุณไม่สามารถสร้างอินสแตนซ์หมวดหมู่ที่ดีกับชุดของวัตถุ ( a, bและc) คุณสามารถสร้างคลาสหมวดหมู่ที่กำหนดเองได้

ลูกศร

หากฟังก์ชั่นของคุณมีความคิดเกี่ยวกับผลิตภัณฑ์และสามารถฉีดฟังก์ชั่นโดยพลการได้ดีกว่าลูกศรสำหรับคุณ

 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 มีแนวคิดเกี่ยวกับแอปพลิเคชันซึ่งมีความสำคัญต่อสิ่งที่คุณต้องการ

Applicatives

ผู้สมัครมีความคิดเกี่ยวกับแอปพลิเคชันของคุณฉันใช้มันใน AST เพื่อเป็นตัวแทนของแอปพลิเคชันฟังก์ชัน

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

มีแนวคิดอื่น ๆ อีกมากมาย แต่ชุดรูปแบบทั่วไปคือการสร้างโครงสร้างข้อมูลที่แสดงถึงหน้าที่ของคุณและส่งต่อไปยังฟังก์ชันการตีความ

นอกจากนี้ยังมีวิธีการมากมายของ monads ฟรี ฉันขอแนะนำให้ poking ที่สิ่งเหล่านี้ถ้าคุณรู้สึกกล้าหาญพวกเขาเป็นเครื่องมืออันทรงพลังสำหรับสิ่งที่คุณกำลังแนะนำและให้คุณสร้างโครงสร้างข้อมูลโดยใช้doสัญกรณ์ . แต่ความสวยงามก็คือฟังก์ชั่นเหล่านี้ใช้งานได้เพียงกับโครงสร้างข้อมูลและไม่ได้ตระหนักถึงวิธีการที่คุณทำมันทั้งหมด นี่คือสิ่งที่ฉันอยากจะแนะนำให้คุณเป็นตัวอย่างของล่าม


หมวดหมู่ดูเหมือนว่าแอปพลิเคขาด ($)- ลูกศรมีลักษณะเหมือน overkill ขนาดใหญ่ตั้งแต่แรกเห็น แต่ยังArrowApplyฟังดูดี - ตราบใดที่ฉันไม่ต้องการให้อะไรฉันก็ไม่สามารถทำได้ +1 ในขณะนี้ด้วยการตรวจสอบเพิ่มเติมที่ต้องทำ
Steve314

3
@ Steve314 หมวดหมู่ไม่มีแอพพลิเคชั่น แต่ monads ขาดวิธีสากลในการรันมันไม่ได้หมายความว่ามันไม่มีประโยชน์เลย
Daniel Gratzer

มีเหตุผลทั่วไปว่าทำไมฉันไม่สามารถใช้ApplicativeหรือArrow(หรือMonad) - ฉันไม่สามารถห่อฟังก์ชั่นปกติ (โดยทั่วไป) เพราะค่าประเภทที่ฉันเป็นตัวแทนฟังก์ชั่น แต่มีการแสดงข้อมูลและจะไม่สนับสนุนฟังก์ชั่นโดยพลการถ้า มีวิธีการแปล นั่นหมายความว่าฉันไม่สามารถให้pure, arrหรือreturnสำหรับอินสแตนซ์ BTW - คลาสเหล่านั้นมีประโยชน์ แต่ฉันไม่สามารถใช้เพื่อจุดประสงค์นี้โดยเฉพาะ Arrowไม่ใช่ "overkill ขนาดใหญ่" - นั่นเป็นความรู้สึกที่ผิดพลาดตั้งแต่ครั้งสุดท้ายที่ฉันพยายามอ่านกระดาษเมื่อฉันไม่พร้อมที่จะเข้าใจ
Steve314

@ Steve314 แนวคิดของการจัดหาอินเทอร์เฟซ monad เพื่อสร้างข้อมูลคือสิ่งที่ monads ฟรีใช้สำหรับตรวจสอบพวกเขา
Daniel Gratzer

ฉันดูวิดีโอจาก Haskell Exchange 2013 - Andres Löhอธิบายได้ดีแม้ว่าฉันจะยังคงต้องดูอีกครั้งเล่นด้วยเทคนิค ฯลฯ ฉันไม่แน่ใจว่ามันจำเป็นที่นี่ เป้าหมายของฉันคือการให้นามธรรมของฟังก์ชั่นโดยใช้การเป็นตัวแทนที่ไม่ใช่ฟังก์ชั่น (แต่ที่มีฟังก์ชั่นล่าม) ฉันไม่ต้องการผลข้างเคียงที่เป็นนามธรรมและฉันไม่จำเป็นต้องมีสัญลักษณ์ที่ชัดเจนสำหรับการดำเนินการตามลำดับ เมื่อมีการใช้ฟังก์ชันนามธรรมแอพพลิเคชั่นและองค์ประกอบจะทำเพียงครั้งเดียวภายในอัลกอริทึมในไลบรารีอื่น
Steve314

2

ในขณะที่คุณชี้ปัญหาหลักกับการใช้ applicative pureนี่เป็นที่ที่ไม่มีความหมายที่เหมาะสมสำหรับ ดังนั้นจึงApplyถูกคิดค้นขึ้น อย่างน้อยนั่นคือความเข้าใจของฉัน

แต่น่าเสียดายที่ผมไม่ได้มีตัวอย่างอยู่ในมือของกรณีที่ไม่ได้ยังApply Applicativeมันอ้างว่านี่เป็นเรื่องจริงIntMapแต่ฉันไม่รู้ว่าทำไม ในทำนองเดียวกันฉันไม่ทราบว่าตัวอย่างของคุณ - ชดเชยจำนวนเต็ม - ยอมรับApplyอินสแตนซ์


อ่านมากขึ้นเช่นความเห็นดูวิธีการตอบ
gnat

ขอโทษ นี่เป็นคำตอบแรกของฉันไม่มากก็น้อย
user185657

คุณจะแนะนำให้ฉันปรับปรุงคำตอบได้อย่างไร
user185657

ลองพิจารณาการแก้ไขเพื่อช่วยให้ผู้อ่านเห็นว่าคำตอบของคุณตอบคำถามอย่างไร“ เหตุใดจึงไม่มี typeclass สำหรับฟังก์ชั่น” เป็นเพราะ“ ไม่มี” หรือ“ เพราะมันไม่มีประโยชน์อย่างที่คุณคิด” หรืออาจจะเป็น มีปัญหาพื้นฐานกับความคิดเหรอ? "
ริ้น

1
ฉันหวังว่านี่จะดีกว่า
user185657

1

นอกจากนี้ยังกล่าวถึงCategory, ArrowและApplicative:

ฉันยังค้นพบData.Lambdaโดย Conal Elliott:

บางคลาสที่มีฟังก์ชั่นเหมือนมีการสร้างแลมบ์ดา

แน่นอนว่าน่าสนใจ แต่ยากที่จะเข้าใจโดยไม่มีตัวอย่าง ...

ตัวอย่าง

ตัวอย่างสามารถพบได้ในหน้า wiki เกี่ยวกับค่าที่จับต้องได้ (TV)ที่ดูเหมือนจะเป็นหนึ่งในสิ่งที่ทำให้เกิดการสร้างTypeComposeห้องสมุด ดูอินพุตและเอาต์พุตฟังก์ชั่นที่มีมูลค่า

แนวคิดของห้องสมุดทีวีคือการแสดงค่า Haskell (รวมถึงฟังก์ชั่น) ในลักษณะที่จับต้องได้

ในการปฏิบัติตามกฎ StackOverflow เกี่ยวกับการไม่โพสต์เปล่าเปลี่ยวฉันคัดลอกบิตด้านล่างซึ่งควรให้ความคิดเกี่ยวกับสิ่งเหล่านี้:

ตัวอย่างแรกอ่าน:

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 (+)

ซึ่งให้เมื่อทำงานเป็นrunIO shopping(ดูที่นั่นสำหรับความคิดเห็นเพิ่มเติม GUIs และตัวอย่างเพิ่มเติม):

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

คำถามนี้ถามคำถามได้อย่างไร ดูวิธีการตอบ
gnat

@gnat ฉันคิดว่าคำจำกัดความในการData.Lambdaให้คลาสสำหรับสิ่งที่คล้ายฟังก์ชั่น (ซึ่งถูกถาม) ... ฉันไม่แน่ใจว่าจะใช้สิ่งเหล่านี้อย่างไร ฉันสำรวจมันเล็กน้อย อาจเป็นไปได้ว่าพวกเขาไม่ได้ให้นามธรรมสำหรับการประยุกต์ใช้ฟังก์ชั่น
imz - Ivan Zakharyaschev
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.