เป็นไปได้หรือไม่ที่จะ“ อบมิติเป็นประเภท” ใน Haskell?


20

สมมติว่าฉันต้องการเขียนไลบรารีที่เกี่ยวข้องกับเวกเตอร์และเมทริกซ์ เป็นไปได้หรือไม่ที่จะอบมิติข้อมูลให้เป็นประเภทเพื่อให้การดำเนินการของมิติที่เข้ากันไม่ได้สร้างข้อผิดพลาดในเวลารวบรวม

ตัวอย่างเช่นฉันต้องการให้ลายเซ็นของผลิตภัณฑ์ดอทเป็นแบบนี้

dotprod :: Num a, VecDim d => Vector a d -> Vector a d -> a

โดยที่dtype นั้นมีค่าจำนวนเต็มเดียว (แสดงถึงมิติของเวกเตอร์เหล่านี้)

ฉันคิดว่านี้สามารถทำได้โดยการกำหนด (ด้วยมือ) VecDimเป็นชนิดที่แยกต่างหากสำหรับแต่ละจำนวนเต็มและกลุ่มพวกเขาในระดับชนิดที่เรียกว่า มีกลไกในการ "สร้าง" ประเภทดังกล่าวหรือไม่?

หรือบางทีวิธีที่ดีกว่า / ง่ายกว่าในการบรรลุสิ่งเดียวกัน?


3
ใช่ถ้าฉันจำได้อย่างถูกต้องมีห้องสมุดที่จะให้พิมพ์ขั้นพื้นฐานนี้ขึ้นอยู่กับใน Haskell ฉันไม่คุ้นเคยเพียงพอที่จะให้คำตอบที่ดี
Telastyn

เมื่อมองไปรอบ ๆ ดูเหมือนว่าtensorห้องสมุดได้รับสิ่งนี้อย่างงดงามโดยใช้dataคำจำกัดความซ้ำ: noaxiom.org/tensor-documentation#ordinals
mitchus

นี่คือสกาล่าไม่ใช่ฮาเซล แต่มีแนวคิดที่เกี่ยวข้องบางอย่างเกี่ยวกับการใช้ชนิดที่อ้างอิงเพื่อป้องกันมิติที่ไม่ตรงกันและ "ประเภท" ที่ไม่ตรงกันของเวกเตอร์ chrisstucchio.com/blog/2014/…
Daenyth

คำตอบ:


32

หากต้องการขยายคำตอบของ @ KarlBielefeldt ต่อไปนี้เป็นตัวอย่างที่สมบูรณ์ของวิธีการใช้งานเวกเตอร์ - รายการที่มีจำนวนองค์ประกอบที่รู้จักกันแบบคงที่ - ใน Haskell ยึดมั่นในหมวกของคุณ ...

{-# LANGUAGE DataKinds #-}
{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE DeriveFoldable #-}
{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE DeriveTraversable #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE TypeFamilies #-}

import Prelude hiding (foldr, zipWith)
import qualified Prelude
import Data.Type.Equality
import Data.Foldable
import Data.Traversable

ดังที่คุณเห็นจากรายการLANGUAGEคำสั่งแบบยาวสิ่งนี้จะใช้ได้กับ GHC รุ่นล่าสุดเท่านั้น

เราต้องการวิธีการแสดงความยาวภายในระบบประเภท ตามนิยามจำนวนธรรมชาติเป็นศูนย์ ( Z) หรือเป็นตัวตายตัวแทนของจำนวนธรรมชาติอื่น ๆ ( S n) ดังนั้นสำหรับตัวอย่างเช่นจำนวน 3 S (S (S Z))จะเขียน

data Nat = Z | S Nat

ด้วยส่วนขยาย DataKindsการdataประกาศนี้จะแนะนำชนิดที่เรียกว่าNatและตัวสร้างประเภทสองที่เรียกว่าSและZ- ในคำอื่น ๆ ที่เรามีจำนวนธรรมชาติระดับประเภท โปรดทราบว่าประเภทSและZไม่มีค่าสมาชิกใด ๆ - ประเภทเท่านั้นที่*เป็นที่อยู่อาศัยของค่า

ตอนนี้เราแนะนำGADT ที่เป็นตัวแทนของเวกเตอร์ที่มีความยาวเป็นที่รู้จัก หมายเหตุลายเซ็นชนิด: Vecต้องการชนิดของชนิดNat (เช่น a ZหรือSชนิด) เพื่อแสดงความยาว

data Vec :: Nat -> * -> * where
    VNil :: Vec Z a
    VCons :: a -> Vec n a -> Vec (S n) a
deriving instance (Show a) => Show (Vec n a)
deriving instance Functor (Vec n)
deriving instance Foldable (Vec n)
deriving instance Traversable (Vec n)

คำจำกัดความของเวกเตอร์นั้นคล้ายกับรายการที่เชื่อมโยงพร้อมกับข้อมูลระดับประเภทพิเศษบางอย่างเกี่ยวกับความยาวของมัน เวกเตอร์มีทั้งVNilในกรณีที่มันมีความยาวZ(ero) หรือเป็นVConsเซลล์ที่เพิ่มรายการลงในเวกเตอร์อื่นซึ่งในกรณีนี้ความยาวของมันจะมากกว่าเวกเตอร์อื่น ( S n) nหมายเหตุว่าไม่มีข้อโต้แย้งสร้างของประเภท มันใช้เวลารวบรวมเพื่อติดตามความยาวและจะถูกลบก่อนคอมไพเลอร์สร้างรหัสเครื่อง

เราได้นิยามประเภทเวกเตอร์ที่มีความรู้เกี่ยวกับความยาวคงที่ ลองค้นหาประเภทของสองสามVecเพื่อทำความเข้าใจกับวิธีการทำงานของมัน:

ghci> :t (VCons 'a' (VCons 'b' VNil))
(VCons 'a' (VCons 'b' VNil)) :: Vec ('S ('S 'Z)) Char  -- (S (S Z)) means 2
ghci> :t (VCons 13 (VCons 11 (VCons 3 VNil)))
(VCons 13 (VCons 11 (VCons 3 VNil))) :: Num a => Vec ('S ('S ('S 'Z))) a  -- (S (S (S Z))) means 3

ผลิตภัณฑ์ dot ดำเนินการเช่นเดียวกับรายการ:

-- note that the two Vec arguments are declared to have the same length
vap :: Vec n (a -> b) -> Vec n a -> Vec n b
vap VNil VNil = VNil
vap (VCons f fs) (VCons x xs) = VCons (f x) (vap fs xs)

zipWith :: (a -> b -> c) -> Vec n a -> Vec n b -> Vec n c
zipWith f xs ys = fmap f xs `vap` ys

dot :: Num a => Vec n a -> Vec n a -> a
dot xs ys = foldr (+) 0 $ zipWith (*) xs ys

vapซึ่ง 'zippily' ใช้เวกเตอร์ของฟังก์ชั่นเพื่อเวกเตอร์ของการขัดแย้งเป็นVec's applicative <*>; ฉันไม่ได้ใส่ไว้ในApplicativeอินสแตนซ์เพราะมันได้รับยุ่ง ยังทราบว่าผมใช้จากอินสแตนซ์คอมไพเลอร์ที่สร้างจากfoldrFoldable

ลองดูสิ:

ghci> let v1 = VCons 2 (VCons 1 VNil)
ghci> let v2 = VCons 4 (VCons 5 VNil)
ghci> v1 `dot` v2
13
ghci> let v3 = VCons 8 (VCons 6 (VCons 1 VNil))
ghci> v1 `dot` v3
<interactive>:20:10:
    Couldn't match type ‘'S 'Z’ with ‘'Z’
    Expected type: Vec ('S ('S 'Z)) a
      Actual type: Vec ('S ('S ('S 'Z))) a
    In the second argument of ‘dot’, namely ‘v3’
    In the expression: v1 `dot` v3

ที่ดี! คุณได้รับข้อผิดพลาดในการคอมไพล์เวลาเมื่อคุณพยายามdotเวกเตอร์ที่มีความยาวไม่ตรงกัน


นี่คือความพยายามที่ฟังก์ชันเชื่อมต่อเวกเตอร์เข้าด้วยกัน:

-- This won't compile because the type checker can't deduce the length of the returned vector
-- VNil +++ ys = ys
-- (VCons x xs) +++ ys = VCons x (concat xs ys)

ความยาวของเวกเตอร์เอาต์พุตจะเป็นผลรวมของความยาวของเวกเตอร์อินพุตสองตัว เราต้องสอนตัวตรวจสอบชนิดวิธีการเพิ่มNats เข้าด้วยกัน สำหรับสิ่งนี้เราใช้ฟังก์ชั่นระดับประเภท :

type family (n :: Nat) :+: (m :: Nat) :: Nat where
    Z :+: m = m
    (S n) :+: m = S (n :+: m)

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

infixr 5 +++
(+++) :: Vec n a -> Vec m a -> Vec (n :+: m) a
VNil +++ ys = ys
(VCons x xs) +++ ys = VCons x (concat xs ys)

นี่คือวิธีที่คุณใช้:

ghci> VCons 1 (VCons 2 VNil) +++ VCons 3 (VCons 4 VNil)
VCons 1 (VCons 2 (VCons 3 (VCons 4 VNil)))

จนถึงขั้นตอนง่ายๆ แล้วถ้าเราต้องการทำสิ่งที่ตรงกันข้ามกับการต่อเรียงและแยกเวกเตอร์เป็นสอง? ความยาวของเวกเตอร์เอาต์พุตขึ้นอยู่กับค่ารันไทม์ของอาร์กิวเมนต์ เราต้องการเขียนสิ่งนี้:

-- this won't work because there aren't any values of type `S` and `Z`
-- split :: (n :: Nat) -> Vec (n :+: m) a -> (Vec n a, Vec m a)

แต่น่าเสียดายที่ Haskell จะไม่ยอมให้เราทำเช่นนั้น การอนุญาตให้ค่าของnอาร์กิวเมนต์ปรากฏในชนิดส่งคืน (ซึ่งโดยทั่วไปเรียกว่าฟังก์ชันที่ขึ้นต่อกันหรือชนิด pi ) จะต้องใช้ชนิดที่ต้องพึ่งพา "full-spectrum" ในขณะที่DataKindsให้เพียงตัวสร้างประเภทที่เลื่อนระดับเท่านั้น หากต้องการกล่าวอีกวิธีหนึ่งตัวสร้างประเภทSและZไม่ปรากฏที่ระดับค่า เราจะต้องชำระค่าซิงเกิลตันสำหรับการแสดงค่าที่แน่นอนNat*

data Natty (n :: Nat) where
    Zy :: Natty Z  -- pronounced 'zed-y'
    Sy :: Natty n -> Natty (S n)  -- pronounced 'ess-y'
deriving instance Show (Natty n)

สำหรับชนิดที่กำหนดn(กับทุกชนิดNat) Natty nมีอย่างแม่นยำระยะหนึ่งประเภท เราสามารถใช้ค่าซิงเกิลตันเป็นพยานในการทำn: เรียนรู้เกี่ยวกับการNattyสอนเราเกี่ยวกับมันnและในทางกลับกัน

split :: Natty n ->
         Vec (n :+: m) a ->  -- the input Vec has to be at least as long as the input Natty
         (Vec n a, Vec m a)
split Zy xs = (Nil, xs)
split (Sy n) (Cons x xs) = let (ys, zs) = split n xs
                           in (Cons x ys, zs)

มาลองปั่นกันดู:

ghci> split (Sy (Sy Zy)) (VCons 1 (VCons 2 (VCons 3 VNil)))
(VCons 1 (VCons 2 VNil), VCons 3 VNil)
ghci> split (Sy (Sy Zy)) (VCons 3 VNil)
<interactive>:116:21:
    Couldn't match type ‘'S ('Z :+: m)’ with ‘'Z’
    Expected type: Vec ('S ('S 'Z) :+: m) a
      Actual type: Vec ('S 'Z) a
    Relevant bindings include
      it :: (Vec ('S ('S 'Z)) a, Vec m a) (bound at <interactive>:116:1)
    In the second argument of ‘split’, namely ‘(VCons 3 VNil)’
    In the expression: split (Sy (Sy Zy)) (VCons 3 VNil)

ในตัวอย่างแรกเราแยกเวกเตอร์สามองค์ประกอบที่ตำแหน่ง 2 ได้สำเร็จ จากนั้นเราได้รับข้อผิดพลาดประเภทเมื่อเราพยายามแยกเวกเตอร์ที่ตำแหน่งที่ผ่านมาจนจบ Singletons เป็นเทคนิคมาตรฐานสำหรับการสร้างประเภทขึ้นอยู่กับค่าใน Haskell

* singletonsไลบรารีนี้มีตัวช่วยเทมเพลต Haskell เพื่อสร้างค่าแบบซิงเกิลเช่นเดียวNattyกับคุณ


ตัวอย่างสุดท้าย แล้วเมื่อคุณไม่รู้มิติของเวกเตอร์แบบสแตติก? ตัวอย่างเช่นถ้าเราพยายามสร้างเวกเตอร์จากข้อมูลรันไทม์ในรูปแบบของรายการ คุณต้องการชนิดของเวกเตอร์เพื่อขึ้นอยู่กับความยาวของรายการอินพุต เพื่อให้เป็นอีกวิธีหนึ่งเราไม่สามารถใช้foldr VCons VNilสร้างเวกเตอร์ได้เนื่องจากประเภทของเวกเตอร์เอาต์พุตเปลี่ยนไปด้วยการวนซ้ำแต่ละครั้ง เราต้องรักษาความลับของเวกเตอร์จากคอมไพเลอร์

data AVec a = forall n. AVec (Natty n) (Vec n a)
deriving instance (Show a) => Show (AVec a)

fromList :: [a] -> AVec a
fromList = Prelude.foldr cons nil
    where cons x (AVec n xs) = AVec (Sy n) (VCons x xs)
          nil = AVec Zy VNil

AVecเป็นประเภทที่มีอยู่ : ตัวแปรชนิดnไม่ปรากฏในประเภทส่งคืนของตัวAVecสร้างข้อมูล เราใช้มันเพื่อจำลองคู่ที่ขึ้นต่อกัน: fromListไม่สามารถบอกความยาวของเวกเตอร์แบบคงที่ แต่มันสามารถคืนสิ่งที่คุณสามารถจับคู่รูปแบบเพื่อเรียนรู้ความยาวของเวกเตอร์ - Natty nในองค์ประกอบแรกของ tuple . ดังที่ Conor McBride กล่าวไว้ในคำตอบที่เกี่ยวข้องว่า "คุณมองสิ่งหนึ่งและในการทำเช่นนั้นเรียนรู้เกี่ยวกับสิ่งอื่น"

นี่เป็นเทคนิคทั่วไปสำหรับประเภทปริมาณที่มีอยู่ เพราะคุณไม่สามารถทำอะไรกับข้อมูลที่คุณไม่รู้ประเภท - ลองเขียนฟังก์ชั่นของdata Something = forall a. Sth a- อัตถิภาวนิยมมักจะมาพร้อมกับหลักฐาน GADT ซึ่งช่วยให้คุณสามารถกู้คืนประเภทเดิมโดยทำการทดสอบการจับคู่รูปแบบ รูปแบบทั่วไปอื่น ๆ สำหรับการดำรงอยู่รวมถึงฟังก์ชั่นการบรรจุเพื่อประมวลผลประเภทของคุณ ( data AWayToGetTo b = forall a. HeresHow a (a -> b)) ซึ่งเป็นวิธีที่เรียบร้อยในการทำโมดูลชั้นหนึ่งหรือการสร้างในพจนานุกรมคลาสประเภท ( data AnOrd = forall a. Ord a => AnOrd a) ซึ่งสามารถช่วยเลียนแบบ polymorphism ย่อย

ghci> fromList [1,2,3]
AVec (Sy (Sy (Sy Zy))) (VCons 1 (VCons 2 (VCons 3 Nil)))

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

filter :: (a -> Bool) -> Vec n a -> AVec a
filter f = foldr (\x (AVec n xs) -> if f x
                                    then AVec (Sy n) (VCons x xs)
                                    else AVec n xs) (AVec Zy VNil) 

สำหรับdotสองAVecเราต้องพิสูจน์ให้ GHC ว่าความยาวเท่ากัน Data.Type.Equalityกำหนด GADT ซึ่งสามารถสร้างได้ก็ต่อเมื่ออาร์กิวเมนต์ประเภทนั้นเหมือนกัน:

data (a :: k) :~: (b :: k) where
    Refl :: a :~: a  -- short for 'reflexivity'

เมื่อคุณจับคู่รูปแบบReflGHC จะรู้สิ่งa ~ bนั้น นอกจากนี้ยังมีฟังก์ชั่นบางอย่างที่ช่วยให้คุณทำงานกับประเภทนี้: เราจะใช้gcastWithในการแปลงระหว่างประเภทที่เทียบเท่าและTestEqualityเพื่อกำหนดว่าสองNattys เท่ากันหรือไม่

ในการทดสอบความเท่าเทียมกันของทั้งสองNattyของเรากำลังจะจำเป็นที่จะต้องใช้ประโยชน์จากความจริงที่ว่าถ้าตัวเลขสองมีค่าเท่ากันแล้วสืบทอดนอกจากนี้ยังเท่ากับ ( :~:เป็นสอดคล้องกันมากกว่าS):

congSuc :: (n :~: m) -> (S n :~: S m)
congSuc Refl = Refl

รูปแบบการจับคู่บนReflด้านซ้ายมือช่วยให้ GHC n ~ mรู้ว่า ด้วยความรู้นั้นมันเป็นเรื่องเล็กน้อยS n ~ S mดังนั้น GHC จึงช่วยให้เรากลับมาใหม่ได้Reflทันที

ตอนนี้เราสามารถเขียนตัวอย่างของการTestEqualityเรียกซ้ำโดยตรงไปตรงมา หากตัวเลขทั้งสองเป็นศูนย์พวกเขาจะเท่ากัน หากตัวเลขทั้งสองมีค่าก่อนหน้าพวกเขาจะเท่ากันถ้ารุ่นก่อนมีค่าเท่ากัน (หากไม่เท่ากันให้ส่งคืนNothing)

instance TestEquality Natty where
    -- testEquality :: Natty n -> Natty m -> Maybe (n :~: m)
    testEquality Zy Zy = Just Refl
    testEquality (Sy n) (Sy m) = fmap congSuc (testEquality n m)  -- check whether the predecessors are equal, then make use of congruence
    testEquality Zy _ = Nothing
    testEquality _ Zy = Nothing

ตอนนี้เราสามารถนำชิ้นส่วนเข้าด้วยกันเพื่อdotคู่AVecของความยาวที่ไม่รู้จัก

dot' :: Num a => AVec a -> AVec a -> Maybe a
dot' (AVec n u) (AVec m v) = fmap (\proof -> gcastWith proof (dot u v)) (testEquality n m)

ขั้นแรกให้จับคู่รูปแบบกับตัวAVecสร้างเพื่อดึงการแสดงแบบไทม์ของความยาวของเวกเตอร์ ตอนนี้ใช้testEqualityเพื่อตรวจสอบว่าความยาวเหล่านั้นเท่ากันหรือไม่ ถ้าเป็นเช่นนั้นเราจะได้Just Refl; gcastWithจะใช้การพิสูจน์ความเท่าเทียมกันนั้นเพื่อให้แน่ใจว่าdot u vพิมพ์ได้ดีโดยการปล่อยn ~ mสมมุติฐานโดยนัย

ghci> let v1 = fromList [1,2,3]
ghci> let v2 = fromList [4,5,6]
ghci> let v3 = fromList [7,8]
ghci> dot' v1 v2
Just 32
ghci> dot' v1 v3
Nothing  -- they weren't the same length

dot :: Num a => [a] -> [a] -> Maybe aโปรดทราบว่าเนื่องจากเวกเตอร์โดยปราศจากความรู้คงที่ของความยาวของมันเป็นพื้นรายการเราได้อย่างมีประสิทธิภาพอีกครั้งดำเนินการรุ่นที่รายชื่อของ ความแตกต่างคือว่ารุ่นนี้จะดำเนินการในแง่ของเวกเตอร์ dotนี่คือจุด: ก่อนที่จะตรวจสอบชนิดจะช่วยให้คุณสามารถโทรdot, คุณต้องมีการทดสอบtestEqualityว่ารายการการป้อนข้อมูลที่มีความยาวเดียวกันโดยใช้ ฉันมีแนวโน้มที่จะได้รับ - ifสถานะผิดทางรอบ แต่ไม่ได้อยู่ในการตั้งค่าที่พิมพ์พึ่งพา!

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

เนื่องจากNothingมีข้อมูลไม่มากคุณสามารถปรับแต่งประเภทของdot'การส่งคืนหลักฐานเพิ่มเติมว่าความยาวไม่เท่ากัน (ในรูปแบบของหลักฐานว่าความแตกต่างของพวกเขาไม่ใช่ 0) ในกรณีความล้มเหลว นี่คล้ายกับเทคนิค Haskell มาตรฐานในการใช้Either String aเพื่อส่งคืนข้อความแสดงข้อผิดพลาดแม้ว่าข้อความพิสูจน์จะมีประโยชน์มากกว่าการคำนวณสตริงมาก!


ดังนั้นจะจบการเดินทางด้วยการเป่านกหวีดของเทคนิคบางอย่างที่ใช้กันทั่วไปในการเขียนโปรแกรม Haskell การเขียนโปรแกรมประเภทนี้ใน Haskell นั้นเจ๋งมาก แต่ก็น่าอึดอัดใจในเวลาเดียวกัน การแบ่งข้อมูลที่ต้องพึ่งพาทั้งหมดของคุณให้เป็นตัวแทนจำนวนมากซึ่งหมายถึงสิ่งเดียวกัน - NatชนิดNatชนิดNatty nซิงเกิล - ค่อนข้างยุ่งยากจริงๆแม้จะมีเครื่องกำเนิดรหัสเพื่อช่วยให้สำเร็จรูป นอกจากนี้ยังมีข้อ จำกัด เกี่ยวกับสิ่งที่สามารถเลื่อนระดับประเภท แม้ว่ามันจะยั่วเย้า! ใจกระวนกระวายใจที่เป็นไปได้ - ในวรรณคดีมีตัวอย่างใน Haskell พิมพ์อย่างยิ่งprintfอินเตอร์เฟซฐานข้อมูลเครื่องยนต์เค้าโครง UI ...

หากคุณต้องการอ่านเพิ่มเติมมีวรรณกรรมที่เพิ่มขึ้นเกี่ยวกับ Haskell ที่พิมพ์ได้ทั้งที่เผยแพร่และบนไซต์เช่น Stack Overflow เป็นจุดเริ่มต้นที่ดีคือHasochismกระดาษ - กระดาษผ่านไปเช่นนี้มาก (ผู้อื่น) การอภิปรายในส่วนที่เจ็บปวดในรายละเอียดบาง Singletonsกระดาษแสดงให้เห็นถึงเทคนิคของค่าเดี่ยว (เช่น) สำหรับข้อมูลเพิ่มเติมเกี่ยวกับการพิมพ์แบบพึ่งพาโดยทั่วไปการสอนของAgdaเป็นจุดเริ่มต้นที่ดี นอกจากนี้Idrisยังเป็นภาษาที่ใช้ในการพัฒนา (โดยประมาณ) ที่ออกแบบมาให้เป็น "Haskell ที่ขึ้นกับประเภท"Natty


@Benjamin FYI ลิงก์ Idris ในตอนท้ายดูเหมือนว่าจะเสียหาย
Erik Eidt

@ErikEidt อุ๊ปส์ขอบคุณสำหรับการชี้ให้เห็น! ฉันจะอัปเดต
Benjamin Hodgson

14

ที่เรียกว่าพิมพ์ขึ้น เมื่อคุณรู้ชื่อแล้วคุณสามารถค้นหาข้อมูลเพิ่มเติมได้มากกว่าที่คุณต้องการ นอกจากนี้ยังมีภาษาที่มีลักษณะคล้ายคาเซลที่น่าสนใจที่เรียกว่าไอดริสซึ่งใช้ภาษาเหล่านั้น ผู้เขียนได้ทำการนำเสนอที่ดีจริงๆบางอย่างในหัวข้อที่คุณสามารถหาได้ใน youtube


มันไม่ได้ขึ้นอยู่กับการพิมพ์เลย การพิมพ์ที่ขึ้นอยู่กับการพูดคุยเกี่ยวกับประเภทที่รันไทม์ แต่การทำให้มิติในประเภทสามารถทำได้อย่างง่ายดายในเวลารวบรวม
DeadMG

4
@DeadMG ในทางตรงกันข้ามการเจรจาการพิมพ์ขึ้นเกี่ยวกับค่านิยมที่รวบรวมเวลา ชนิดที่รันไทม์คือการสะท้อนไม่ใช่การพิมพ์ที่ขึ้นอยู่กับ อย่างที่คุณเห็นจากคำตอบของฉันการอบมิติข้อมูลลงในประเภทนั้นไม่ง่ายสำหรับมิติทั่วไป (คุณสามารถกำหนดnewtype Vec2 a = V2 (a,a), newtype Vec3 a = V3 (a,a,a)และอื่น ๆ แต่นั่นไม่ใช่สิ่งที่คำถามที่ถาม.)
เบนจามินฮอดจ์สัน

ค่าจะปรากฏที่รันไทม์เท่านั้นดังนั้นคุณจึงไม่สามารถพูดคุยเกี่ยวกับค่าในเวลารวบรวมได้เว้นแต่คุณต้องการแก้ปัญหาการหยุดชะงัก ทั้งหมดที่ฉันพูดคือแม้ใน C ++ คุณก็สามารถสร้างเทมเพลตในมิติและมันก็ใช้ได้ดี นั่นไม่ได้เทียบเท่าใน Haskell?
DeadMG

4
@DeadMG "Full-spectrum" ภาษาที่พิมพ์ได้อย่างพึ่งพา (เช่น Agda) ทำในความเป็นจริงให้การคำนวณระดับคำโดยพลการในภาษาประเภท ในขณะที่คุณชี้ให้เห็นสิ่งนี้ทำให้คุณเสี่ยงต่อการพยายามแก้ไขปัญหาการหยุดชะงัก ส่วนใหญ่ระบบการพิมพ์ dependently, AFAIK ถ่อในการแก้ปัญหานี้ได้โดยไม่เป็นทัวริงสมบูรณ์ ฉันไม่ใช่คน C ++ แต่ไม่แปลกใจเลยที่คุณสามารถจำลองประเภทผู้ติดตามโดยใช้เทมเพลต เทมเพลตสามารถถูกใช้ในวิธีการสร้างสรรค์ทุกประเภท
Benjamin Hodgson

4
@BenjaminHodgson คุณไม่สามารถดำเนินการกับชนิดเทมเพลตเนื่องจากคุณไม่สามารถจำลองชนิด pi ได้ ประเภทขึ้นอยู่กับ "canonical" จะต้องอ้างว่าคุณต้องการPi (x : A). Bซึ่งเป็นฟังก์ชั่นจากAการB xที่xเป็นอาร์กิวเมนต์ของฟังก์ชั่น นี่คือประเภทการคืนของฟังก์ชันขึ้นอยู่กับการแสดงออกที่ให้เป็นอาร์กิวเมนต์ อย่างไรก็ตามสิ่งเหล่านี้สามารถลบได้มันเป็นเวลารวบรวมเท่านั้น
Daniel Gratzer
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.