กำหนดรายการโดยใช้ระบบพิมพ์ Hindley-Milner เท่านั้น


10

ผมทำงานเกี่ยวกับคอมไพเลอร์แคลคูลัสแลมบ์ดาขนาดเล็กที่มีการทำงาน Hindley-มิลเนอร์ระบบอนุมานชนิดและตอนนี้ยังสนับสนุนการปล่อย recursive ของ (ไม่อยู่ในรหัสที่เชื่อมโยง) ซึ่งผมเข้าใจว่าควรจะเพียงพอที่จะทำให้มันสมบูรณ์ทัวริง

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

วิธีที่ง่ายที่สุดที่ฉันสามารถนึกถึงรายการxคือเป็นสิ่งที่เป็นnull(หรือรายการที่ว่างเปล่า) หรือคู่ที่มีทั้งxรายการxและ แต่การทำเช่นนี้ฉันต้องสามารถกำหนดคู่และหรือของซึ่งฉันเชื่อว่าเป็นผลิตภัณฑ์และประเภทผลรวม

ดูเหมือนว่าฉันสามารถกำหนดคู่ด้วยวิธีนี้:

pair = λabf.fab
first = λp.p(λab.a)
second = λp.p(λab.b)

เนื่องจากpairจะมีคนประเภทที่a -> (b -> ((a -> (b -> x)) -> x))หลังจากผ่านการพูดเป็นintและstringก็ต้องการให้ผลอะไรกับชนิด(int -> (string -> x)) -> xซึ่งจะเป็นตัวแทนของคู่ที่และint stringสิ่งที่รบกวนจิตใจฉันอยู่ที่นี่คือถ้านั่นหมายถึงคู่ทำไมมันไม่สมเหตุสมผลเชิงเหตุผลหรือไม่บอกเป็นนัย ๆint and string? อย่างไรก็ตามเทียบเท่ากับ(((int and string) -> x) -> x)ราวกับว่าฉันสามารถมีชนิดผลิตภัณฑ์เป็นพารามิเตอร์ของฟังก์ชันเท่านั้น คำตอบนี้ดูเหมือนจะแก้ไขปัญหานี้ได้ แต่ฉันไม่รู้ว่าสัญลักษณ์ที่เขาใช้หมายถึงอะไร นอกจากนี้หากสิ่งนี้ไม่ได้เข้ารหัสประเภทผลิตภัณฑ์จริง ๆ ฉันสามารถทำอะไรกับประเภทผลิตภัณฑ์ที่ฉันไม่สามารถทำได้ด้วยคำจำกัดความของคู่ด้านบน (พิจารณาว่าฉันสามารถกำหนด n-tuples ด้วยวิธีเดียวกันได้) ถ้าไม่เช่นนั้นจะไม่ขัดแย้งกับความจริงที่ว่าคุณไม่สามารถแสดงร่วม (AFAIK) โดยใช้นัยเพียงอย่างเดียวได้หรือไม่

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

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

แก้ไข: ฉันไม่แน่ใจว่าชื่อทางเทคนิคของสิ่งที่ฉันได้ดำเนินการจนถึงขณะนี้ แต่ทั้งหมดที่ฉันมีคือรหัสที่ฉันเชื่อมโยงด้านบนซึ่งเป็นอัลกอริทึมการสร้างข้อ จำกัด ที่ใช้กฎสำหรับแอปพลิเคชันนามธรรมและตัวแปร จากอัลกอริทึม Hinley-Milner และอัลกอริทึมการรวมที่ได้รับประเภทหลัก ตัวอย่างเช่นการแสดงออก\a.aจะให้ผลประเภทa -> aและการแสดงออก\a.(a a)จะโยนข้อผิดพลาดการตรวจสอบที่เกิดขึ้น ด้านบนของสิ่งนี้ไม่มีletกฎที่แน่นอนแต่ฟังก์ชั่นที่ดูเหมือนว่าจะมีผลกระทบเหมือนกันที่ช่วยให้คุณกำหนดฟังก์ชั่นส่วนกลางซ้ำเช่นรหัสหลอกนี้:

GetTypeOfGlobalFunction(term, globalScope, nameOfFunction)
{
    // Here 'globalScope' contains a list of name-value pair where every value is of class 'ClosedType', 
    // meaning their type will be cloned before unified in the unification algorithm so that they can be used polymorphically 
    tempType = new TypeVariable() // Assign a dummy type to `tempType`, say, type 'x'.
    // The next line creates an scope with everything in 'globalScope' plus the 'nameOfFunction = tempType' name-value pair
    tempScope = new Scope(globalScope, nameOfFunction, tempType) 
    type = TypeOfTerm(term, tempScope) // Calculate the type of the term 
    Unify(tempType, type)
    return type
    // After returning, the code outside will create a 'ClosedType' using the returned type and add it to the global scope.
}

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

แก้ไข 2:ฉันเพิ่งรู้ว่าฉันต้องการประเภทเรียกซ้ำซึ่งฉันไม่มีเพื่อกำหนดรายการเหมือนที่ฉันต้องการ


คุณเป็นคนที่เจาะจงมากขึ้นเกี่ยวกับสิ่งที่คุณนำไปใช้จริงหรือไม่? คุณได้ใช้แคลคูลัสแลมบ์ดาที่พิมพ์เพียงอย่างเดียว (พร้อมคำจำกัดความซ้ำ) และให้รูปแบบพารามิเตอร์หลากหลายแบบ Hindley-Milner หรือไม่? หรือคุณใช้แคลคูลัสแลมบ์ดา polymorphic ลำดับที่สอง?
Andrej Bauer

บางทีฉันอาจถามได้ง่ายขึ้น: ถ้าฉันใช้ OCaml หรือ SML และ จำกัด ให้เป็นข้อกำหนดแลมบ์ดาบริสุทธิ์และคำจำกัดความซ้ำแล้วซ้ำอีกนั่นคือสิ่งที่คุณกำลังพูดถึงหรือไม่
Andrej Bauer

@AndrejBauer: ฉันได้แก้ไขคำถาม ฉันไม่แน่ใจเกี่ยวกับ OCaml และ SML แต่ฉันค่อนข้างแน่ใจว่าถ้าคุณใช้ Haskell และ จำกัด ให้เป็นคำศัพท์แลมบ์ดาและการเรียกซ้ำแบบเรียกซ้ำ (เช่นlet func = \x -> (func x)) คุณจะได้สิ่งที่ฉันมี
Juan

1
หากต้องการปรับปรุงคำถามของคุณให้เช็คเอาต์โพสต์เมตานี้
Juho

คำตอบ:


13

คู่

การเข้ารหัสนี้เป็นการเข้ารหัสของคู่คริสตจักร เทคนิคที่คล้ายกันสามารถเข้ารหัส booleans, จำนวนเต็ม, รายการ, และโครงสร้างข้อมูลอื่น ๆ

ภายใต้บริบทx:a; y:bคำว่ามีชนิดpair x y (a -> b -> t) -> tการตีความเชิงตรรกะของประเภทนี้คือสูตรต่อไปนี้ (ฉันใช้สัญลักษณ์ทางคณิตศาสตร์มาตรฐาน: is implication, is หรือ is และ is negation;เทียบเท่ากับสูตร): ทำไม“ และหรือ ” อย่างสังหรณ์ใจเพราะ¬

(abt)t¬(¬a¬bt)t(ab¬t)t(ab)t
ab tpairเป็นฟังก์ชันที่รับฟังก์ชั่นเป็นอาร์กิวเมนต์และใช้กับทั้งคู่ มีสองวิธีนี้สามารถไป: ฟังก์ชั่นการโต้แย้งสามารถใช้ประโยชน์จากคู่หรือมันสามารถสร้างมูลค่าของประเภทtโดยไม่ต้องใช้คู่เลย

pairเป็นตัวสร้างสำหรับประเภทคู่และfirstและsecondเป็น destructors (คำเหล่านี้เป็นคำเดียวกับที่ใช้ในการเขียนโปรแกรมเชิงวัตถุนี่คือคำที่มีความหมายซึ่งเกี่ยวข้องกับการตีความเชิงตรรกะของประเภทและคำศัพท์ที่ฉันจะไม่เข้าไปที่นี่) สัญชาตญาณช่วยให้คุณเข้าถึงสิ่งที่เป็น ในวัตถุและคอนสตรัคเตอร์ปูทางสำหรับ destructor โดยรับอาร์กิวเมนต์เป็นฟังก์ชันที่ใช้กับส่วนต่าง ๆ ของวัตถุ หลักการนี้สามารถนำไปใช้กับประเภทอื่น ๆ

ผลรวม

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

let case w = λf. λg. w f g           case : ((a->t) -> (b->t) -> t) -> (a->t) -> (b->t) -> t
  (* or simply let case w = w *)
let left x = λf. λg. f x             left : a -> ((a->t) -> (b->t) -> t)
let right y = λf. λg. g x            right : b -> ((a->t) -> (b->t) -> t)

ผมขอย่อชนิดเป็น(a->t) -> (b->t) -> t SUM(a,b)(t)ดังนั้นประเภทของ destructors และ constructors คือ:

case : SUM(a,b)(t) -> (a->t) -> (b->t) -> t
left : a -> SUM(a,b)(t)
right : b -> SUM(a,b)(t)

ดังนั้น

case (left x) f g → f x
case (rightt y) f g → g y

รายการ

สำหรับรายการใช้หลักการเดียวกันอีกครั้ง รายการที่องค์ประกอบมีชนิดaสามารถสร้างได้สองวิธี: อาจเป็นรายการที่ว่างเปล่าหรืออาจเป็นองค์ประกอบ (ส่วนหัว) บวกกับรายการ (ส่วนท้าย) เมื่อเปรียบเทียบกับคู่มีความแตกต่างเล็กน้อยเมื่อพูดถึง destructors: คุณไม่สามารถแยกได้สอง destructors headและtailเพราะพวกมันจะทำงานเฉพาะในรายการที่ไม่ว่างเปล่า คุณจำเป็นต้องมี destructor เดียวโดยมีสองอาร์กิวเมนต์หนึ่งรายการคือฟังก์ชัน 0 อาร์กิวเมนต์ (เช่นค่า) สำหรับกรณี nil และอีก 2 ฟังก์ชั่นสำหรับอาร์กิวเมนต์ cons ฟังก์ชั่นชอบis_empty, headและtailจะได้รับจากที่ เช่นเดียวกับในกรณีของผลรวมรายการเป็นฟังก์ชัน destructor โดยตรง

let nil = λn. λc. n
let cons h t = λn. λc. c h t
let is_empty l = l true (λh. λt. false) 
let head l default = l default (λh. λt. h)
let tail l default = l default (λh. λt. t)

แต่ละฟังก์ชั่นเหล่านี้คือ polymorphic หากคุณทำงานกับประเภทของฟังก์ชั่นเหล่านี้คุณจะสังเกตได้ว่าconsไม่เหมือนกัน: ประเภทของผลลัพธ์ไม่เหมือนกับประเภทของอาร์กิวเมนต์ ประเภทของผลลัพธ์เป็นตัวแปร - มันเป็นเรื่องทั่วไปมากกว่าการโต้แย้ง หากคุณเชื่อมโยงconsสายการโทรที่ต่อเนื่องเพื่อสร้างรายการอินสแตนซ์consที่ประเภทต่างๆ นี่เป็นสิ่งสำคัญที่จะทำให้รายการทำงานในกรณีที่ไม่มีประเภทแบบเรียกซ้ำ คุณสามารถสร้างรายการที่ต่างกันได้ ในความเป็นจริงประเภทที่คุณสามารถแสดงไม่ใช่ "รายการ " แต่ "รายการที่มีองค์ประกอบแรกเป็นประเภท "TT1,,Tn

ในขณะที่คุณคาดการณ์หากคุณต้องการกำหนดประเภทที่มีเพียงรายการที่เป็นเนื้อเดียวกันคุณจะต้องมีประเภทที่เรียกซ้ำ ทำไม? ลองดูที่ประเภทของรายการ รายการถูกเข้ารหัสเป็นฟังก์ชันซึ่งรับสองอาร์กิวเมนต์: ค่าที่ส่งคืนในรายการว่างและฟังก์ชันคำนวณค่าที่จะส่งคืนบนเซลล์ข้อเสีย อนุญาตaเป็นประเภทองค์ประกอบbเป็นประเภทของรายการและcเป็นประเภทที่ส่งกลับโดย destructor ประเภทของรายการคือ

a -> (a -> b -> c) -> c

การทำรายการที่เป็นเนื้อเดียวกันนั้นบอกว่าถ้ามันเป็นเซลล์ข้อเสียหางจะต้องมีชนิดเดียวกันโดยรวมคือเพิ่มข้อ จำกัด

a -> (a -> b -> c) -> c = b

ระบบชนิด Hindley-Milner สามารถขยายได้ด้วยประเภทแบบเรียกซ้ำและในความเป็นจริงแล้วภาษาการเขียนโปรแกรมเชิงปฏิบัติทำเช่นนั้น ภาษาโปรแกรมเชิงปฏิบัติมักจะไม่อนุญาตสมการ "เปล่า" และต้องการตัวสร้างข้อมูล แต่นี่ไม่ใช่ข้อกำหนดที่แท้จริงของทฤษฎีพื้นฐาน การขอให้ผู้สร้างข้อมูลลดความซับซ้อนของการอนุมานประเภทและในทางปฏิบัติมีแนวโน้มที่จะหลีกเลี่ยงการยอมรับฟังก์ชั่นที่เป็นบั๊ก แต่จริง ๆ แล้วสามารถพิมพ์ได้ด้วยข้อ จำกัด ที่ไม่ได้ตั้งใจซึ่งทำให้เกิดข้อผิดพลาดประเภทที่เข้าใจยาก นี่คือเหตุผลที่ตัวอย่างเช่น OCaml ยอมรับชนิดเรียกซ้ำที่ไม่ได้รับการป้องกันเฉพาะกับ-rectypesตัวเลือกคอมไพเลอร์ที่ไม่ใช่ค่าเริ่มต้น นี่คือคำจำกัดความด้านบนในไวยากรณ์ OCaml พร้อมกับคำจำกัดความประเภทสำหรับรายการที่เป็นเนื้อเดียวกันโดยใช้สัญกรณ์สำหรับaliased ประเภท recursive : type_expression as 'aหมายความว่าชนิดเป็นปึกแผ่นกับตัวแปรtype_expression'a

# let nil = fun n c -> n;;
val nil : 'a -> 'b -> 'a = <fun>
# let cons h t = fun n c -> c h t;;
val cons : 'a -> 'b -> 'c -> ('a -> 'b -> 'd) -> 'd = <fun>
# let is_empty l = l true (fun h t -> false);;
val is_empty : (bool -> ('a -> 'b -> bool) -> 'c) -> 'c = <fun>
# let head l default = l default (fun h t -> h);;
val head : ('a -> ('b -> 'c -> 'b) -> 'd) -> 'a -> 'd = <fun>
# let tail l default = l default (fun h t -> t);;
val tail : ('a -> ('b -> 'c -> 'c) -> 'd) -> 'a -> 'd = <fun>
# type ('a, 'b, 'c) ulist = 'c -> ('a -> 'b -> 'c) -> 'c;;
type ('a, 'b, 'c) ulist = 'c -> ('a -> 'b -> 'c) -> 'c
# is_empty (cons 1 nil);;
- : bool = false
# head (cons 1 nil) 0;;
- : int = 1
# head (tail (cons 1 (cons 2.0 nil)) nil) 0.;;
- : float = 2.

(* -rectypes is required for what follows *)
# type ('a, 'b, 'c) rlist = 'c -> ('a -> 'b -> 'c) -> 'c as 'b;;
type ('a, 'b, 'c) rlist = 'b constraint 'b = 'c -> ('a -> 'b -> 'c) -> 'c
# let rcons = (cons : 'a -> ('a, 'b, 'c) rlist -> ('a, 'b, 'c) rlist);;
val rcons :
  'a ->
  ('a, 'c -> ('a -> 'b -> 'c) -> 'c as 'b, 'c) rlist -> ('a, 'b, 'c) rlist =
  <fun>
# head (rcons 1 (rcons 2 nil)) 0;;
- : int = 1
# tail (rcons 1 (rcons 2 nil)) nil;;
- : 'a -> (int -> 'a -> 'a) -> 'a as 'a = <fun>
# rcons 1 (rcons 2.0 nil);;
Error: This expression has type
         (float, 'b -> (float -> 'a -> 'b) -> 'b as 'a, 'b) rlist = 'a
       but an expression was expected of type
         (int, 'b -> (int -> 'c -> 'b) -> 'b as 'c, 'b) rlist = 'c

กอด

โดยทั่วไปแล้วฟังก์ชั่นที่แสดงถึงโครงสร้างข้อมูลคืออะไร?

  • สำหรับจำนวนธรรมชาติ:ถูกแทนด้วยฟังก์ชันที่วนซ้ำอาร์กิวเมนต์ครั้งnn
  • สำหรับคู่:จะแสดงเป็นฟังก์ชั่นที่ใช้อาร์กิวเมนต์กับและy ที่(x,y)xy
  • สำหรับจำนวนเงินที่:จะแสดงเป็นฟังก์ชั่นที่ใช้ของอาร์กิวเมนต์ TH จะxini(x)ix
  • สำหรับรายการ:แสดงเป็นฟังก์ชันที่รับอาร์กิวเมนต์สองตัวค่าที่ส่งคืนสำหรับรายการว่างและฟังก์ชันเพื่อใช้กับเซลล์ข้อเสีย[x1,,xn]

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


คุณพูดถึง "การเข้ารหัสคริสตจักร " ของจำนวนเต็มคู่ผลรวม แต่สำหรับรายการที่คุณให้การเข้ารหัสของสก็อต ฉันคิดว่ามันอาจจะสับสนเล็กน้อยสำหรับผู้ที่ไม่คุ้นเคยกับการเข้ารหัสประเภทอุปนัย
Stéphane Gimenez

ดังนั้นโดยทั่วไปแล้วประเภทคู่ของฉันไม่ได้เป็นประเภทผลิตภัณฑ์ในฐานะที่เป็นฟังก์ชั่นที่มีประเภทนี้เพียงแค่ส่งคืนtและเพิกเฉยต่ออาร์กิวเมนต์ที่ควรจะใช้aและb(ซึ่งเป็นสิ่งที่(a and b) or tพูด) และดูเหมือนว่าฉันมีปัญหาแบบเดียวกันกับผลรวม และถ้าไม่มีประเภทแบบเรียกซ้ำฉันจะไม่มีรายชื่อที่เหมือนกัน ดังนั้นในสองสามคำคุณกำลังบอกว่าฉันควรเพิ่มกฎรวมผลคูณผลิตภัณฑ์และกฎแบบเรียกซ้ำเพื่อรับรายการที่เป็นเนื้อเดียวกัน
Juan

คุณหมายถึงcase (right y) f g → g yในตอนท้ายของส่วนผลรวมของคุณหรือไม่
Juan

@ StéphaneGimenezฉันไม่ได้ตระหนัก ฉันไม่ชินกับการเข้ารหัสเหล่านี้ในโลกที่พิมพ์ออกมา คุณสามารถให้การอ้างอิงสำหรับการเข้ารหัสของคริสตจักรกับการเข้ารหัสของสกอตต์ได้หรือไม่?
Gilles 'ดังนั้น - หยุดความชั่วร้าย'

@JuanLuisSoldi คุณคงเคยได้ยินว่า“ ไม่มีปัญหาที่แก้ไม่ได้ด้วยการเพิ่มระดับของการอ้อม” การเข้ารหัสคริสตจักรเข้ารหัสโครงสร้างข้อมูลเป็นฟังก์ชันโดยการเพิ่มระดับการเรียกฟังก์ชัน: โครงสร้างข้อมูลจะกลายเป็นฟังก์ชันลำดับที่สองที่คุณใช้กับฟังก์ชันเพื่อดำเนินการกับส่วนต่างๆ ถ้าคุณต้องการชนิดของรายการที่เป็นเนื้อเดียวกันคุณจะต้องจัดการกับความจริงที่ว่าประเภทของหางนั้นเหมือนกับชนิดของรายการทั้งหมด ฉันคิดว่าสิ่งนี้ต้องเกี่ยวข้องกับการเรียกซ้ำประเภทหนึ่ง
Gilles 'SO- หยุดความชั่วร้าย'

2

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

เรากำหนดบูลีนตามปกติ:

true = λi.λe.i
false = λi.λe.e
if = λcond.λthen.λelse.(cond then else)

รายการนั้นเป็นคู่ที่มีองค์ประกอบแรกเป็นบูลีนและองค์ประกอบที่สองเป็นคู่หัว / หาง ฟังก์ชันพื้นฐานบางรายการ:

isNull = λl.(first l)
null = pair false false     --The second element doesn't matter in this case
cons = λh.λt.(pair true (pair h t ))
head = λl.(fst (snd l))   --This is a partial function
tail = λl.(snd (snd l))   --This is a partial function  

map = λf.λl.(if (isNull l)
                 null 
                 (cons (f (head l)) (map f (tail l) ) ) 

แต่นี่จะไม่ให้รายการเอกพันธ์แก่ฉันมันถูกต้องไหม?
Juan
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.